			<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
  	<title>Hutool Wiki_V3.1.0</title>
  	<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
	<style>
		        body {
            color: #666666;
            font-family: 微软雅黑,Verdana,sans-serif,宋体;
            font-size: 14px;
            margin: 0;
            padding: 0;
        }
        a {
            color: #999999;
            text-decoration: none;
        }
        a:hover {
            color: #16B28F;
        }
        .sin_navi ul {
            list-style: none outside none;
            margin: 0;
            padding: 0 0 0 10px;
        }
        .sin_navi ul li {
            line-height: 30px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        li {
            line-height: 24px;
        }
        table {
            border-left: 1px solid #CCCCCC;
            border-top: 1px solid #CCCCCC;
            margin: 5px 10px;
            text-align: left;
            width: 678px;
        }
        table th {
            background-color: #EEEEEE;
            border-bottom: 1px solid #CCCCCC;
            border-right: 1px solid #CCCCCC;
            padding: 10px;
        }
        table td {
            border-bottom: 1px solid #CCCCCC;
            border-right: 1px solid #CCCCCC;
        }
        p {
            line-height: 24px;
            margin: 10px;
        }
        h1, h2, h3, h4, h5, h6 {
            color: #111111;
            font-weight: normal;
            margin: 0;
            padding: 0;
        }
        h1 {
            font-size: 26px;
        }
        h2 {
            border-bottom: 1px dashed #CCCCCC;
            font-size: 24px;
            padding: 10px 0;
        }
        h3 {
            font-size: 20px;
            padding: 8px 0;
        }
        h4 {
            font-size: 18px;
            margin-left: 10px;
            padding: 5px 0;
        }
        h5 {
            font-size: 16px;
            margin-left: 10px;
            padding: 5px 0;
        }
        h6 {
            font-size: 16px;
            margin-left: 10px;
            padding: 5px 0;
        }
        blockquote {
            background: url("../../../img/blockquote.gif") no-repeat scroll left top #F4F5F7;
            margin: 5px 10px;
            padding: 10px 10px 10px 30px;
        }
        pre {
            background: none repeat scroll 0 0 #EEEEEE;
            margin: 5px 10px;
            padding: 10px;
        }
        .sin_callout {
            margin: 5px 10px;
        }
        .sin_header {
            background-color: #F7F7F7;
            border-bottom: 1px solid #CCCCCC;
            height: 80px;
            width: 100%;
        }
        .sin_title {
            margin: 0 auto;
            width: 1000px;
        }
        .sin_title_text {
            color: #000000;
            float: left;
            line-height: 80px;
        }
        .sin_version {
            color: #999999;
            float: left;
            font-size: 14px;
            padding-top: 25px;
            margin-left: 10px;
        }
        .sin_main {
            margin: 0 auto;
            width: 1000px;
        }
        .sin_navi {
            float: left;
            margin-bottom: 20px;
            overflow-y: scroll;
            padding: 0;
            position: relative;
            width: 279px;
            z-index: 1;
        }
        .sin_navi_nomove {
            position: fixed;
            top: 0;
        }
        .sin_navi_ca a {
            color: #333333;
        }
        .sin_navi_ca a:hover {
            color: #16B28F;
        }
        .sin_navi_current {
            border-right: 3px solid #16B28F;
        }
        .sin_navi_current a {
            color: #16B28F;
        }
        .sin_content {
            background-color: #FFFFFF;
            border-left: 1px solid #CCCCCC;
            float: left;
            margin-left: -20px;
            padding: 10px 20px;
            position: relative;
            width: 699px;
            z-index: 2;
        }
        .sin_content_mar {
            margin-left: 259px;
        }
        .sin_footer {
            border-top: 1px solid #CCCCCC;
            color: #999999;
            height: 80px;
            line-height: 80px;
            margin: 0 auto;
            text-align: center;
        }
        .sin_info {
            background: none repeat scroll 0 0 #FFFFCC;
            border: 1px solid #D5D4D4;
            border-radius: 4px 4px 4px 4px;
            color: #999999;
            margin: 10px 0;
            padding: 14px;
        }
        .sin_link_logo {
            color: #16B28F;
            font-size: 12px;
            margin-left: 10px;
        }
        .sin_text {
            margin-bottom: 10px;
        }
        .d_callout {
            margin-bottom: 10px;
        }
        .d_callout_info {
            background: none repeat scroll 0 0 #F4F8FA;
            border-left: 4px solid #5BC0DE;
            padding: 10px;
        }
        .d_callout_warn {
            background: none repeat scroll 0 0 #FCF8F2;
            border-left: 4px solid #F0AD4E;
            padding: 10px;
        }
        .d_callout_danger {
            background: none repeat scroll 0 0 #FDF7F7;
            border-left: 4px solid #D9534F;
            padding: 10px;
        }
        .d_callout_success {
            background: none repeat scroll 0 0 #F3F8F3;
            border-left: 4px solid #50AF51;
            padding: 10px;
        }
        .d_callout input {
            background-color: rgba(0, 0, 0, 0);
            font-size: 15px;
            margin-bottom: 5px;
            padding: 10px 5px 0 10px;
        }
        .d_callout textarea {
            background-color: rgba(0, 0, 0, 0);
        }
        .d_callout_icons a {
            color: #AAAAAA;
            margin-left: 10px;
        }
        a.d_callout_icon_info {
            color: #5BC0DE;
        }
        a.d_callout_icon_warn {
            color: #F0AD4E;
        }
        a.d_callout_icon_danger {
            color: #D9534F;
        }
        a.d_callout_icon_success {
            color: #50AF51;
        }
        .clear {
            clear: both;
        }

	</style>
</head>
<body>
	<div class="sin_header">
		<div class="sin_title">
			<h1 class="sin_title_text">Hutool Wiki</h1>
			<span class="sin_version">V3.1.0</span>
			<div class="clear"></div>
		</div>
		<div id="top_end"></div>
	</div>
	<div class="sin_main">
		<div class="sin_navi" id="sin_navi_id">
			<ul style="margin: 20px 0;">
				    																																													<li style="width: 246px;" id="navi_text_215940">
									<a href="#text_215940">
										前言
									</a>	
								</li>
																																															<li style="width: 246px;" id="navi_text_215941">
									<a href="#text_215941">
										安装
									</a>	
								</li>
																																															<li style="width: 246px;" id="navi_text_215942">
									<a href="#text_215942">
										友情开源项目
									</a>	
								</li>
																															<li class="sin_navi_ca" style="width: 246px;" id="navi_category_52943">
								<a href="#category_52943">
									核心(Hutool-core)
								</a>
							</li>
										<ul>
									<li class="sin_navi_ca" style="width:236px" id="navi_category_52936">
					<a href="#category_52936">
						克隆
					</a>
				</li>
							<ul>
																			<li style="width:226px" id="navi_text_215943">
						<a href="#text_215943">
							支持泛型的克隆接口和克隆类
						</a>	
					</li>
											</ul>
													<li class="sin_navi_ca" style="width:236px" id="navi_category_52937">
					<a href="#category_52937">
						类型转换
					</a>
				</li>
							<ul>
																			<li style="width:226px" id="navi_text_215944">
						<a href="#text_215944">
							类型转换工具类-Convert
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215945">
						<a href="#text_215945">
							自定义类型转换-ConverterRegistry
						</a>	
					</li>
											</ul>
													<li class="sin_navi_ca" style="width:236px" id="navi_category_52938">
					<a href="#category_52938">
						日期时间
					</a>
				</li>
							<ul>
																			<li style="width:226px" id="navi_text_215946">
						<a href="#text_215946">
							概述
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215947">
						<a href="#text_215947">
							日期时间工具-DateUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215948">
						<a href="#text_215948">
							日期时间对象-DateTime
						</a>	
					</li>
											</ul>
													<li class="sin_navi_ca" style="width:236px" id="navi_category_52939">
					<a href="#category_52939">
						IO
					</a>
				</li>
							<ul>
																			<li style="width:226px" id="navi_text_215949">
						<a href="#text_215949">
							概述
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215950">
						<a href="#text_215950">
							IO工具类-IoUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215951">
						<a href="#text_215951">
							文件工具类-FileUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215952">
						<a href="#text_215952">
							文件类型判断-FileTypeUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215953">
						<a href="#text_215953">
							文件监听-WatchMonitor
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215954">
						<a href="#text_215954">
							ClassPath资源访问-ClassPathResource
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215955">
						<a href="#text_215955">
							文件读取-FileReader
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215956">
						<a href="#text_215956">
							文件写入-FileWriter
						</a>	
					</li>
											</ul>
													<li class="sin_navi_ca" style="width:236px" id="navi_category_52940">
					<a href="#category_52940">
						工具类
					</a>
				</li>
							<ul>
																			<li style="width:226px" id="navi_text_215957">
						<a href="#text_215957">
							概述
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215958">
						<a href="#text_215958">
							数组工具-ArrayUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215959">
						<a href="#text_215959">
							Bean工具-BeanUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215960">
						<a href="#text_215960">
							字符编码工具-CharsetUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215961">
						<a href="#text_215961">
							类工具-ClassUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215962">
						<a href="#text_215962">
							集合工具-CollectionUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215963">
						<a href="#text_215963">
							Escape工具-EscapeUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215964">
						<a href="#text_215964">
							Hash算法-HashUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215965">
						<a href="#text_215965">
							16进制工具-HexUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215966">
						<a href="#text_215966">
							身份证工具-IdcardUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215967">
						<a href="#text_215967">
							图片工具-ImageUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215968">
						<a href="#text_215968">
							网络工具-NetUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215969">
						<a href="#text_215969">
							数字工具-NumberUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215970">
						<a href="#text_215970">
							分页工具-PageUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215971">
						<a href="#text_215971">
							对象工具-ObjectUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215972">
						<a href="#text_215972">
							随机工具-RandomUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215973">
						<a href="#text_215973">
							字符串工具-StrUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215974">
						<a href="#text_215974">
							正则工具-ReUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215975">
						<a href="#text_215975">
							线程工具-ThreadUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215976">
						<a href="#text_215976">
							URL工具-URLUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215977">
						<a href="#text_215977">
							XML工具-XmlUtil
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215978">
						<a href="#text_215978">
							压缩工具-ZipUtil
						</a>	
					</li>
											</ul>
													<li class="sin_navi_ca" style="width:236px" id="navi_category_52941">
					<a href="#category_52941">
						语言特性
					</a>
				</li>
							<ul>
																			<li style="width:226px" id="navi_text_215979">
						<a href="#text_215979">
							单例工具-Singleton
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215980">
						<a href="#text_215980">
							base32编码解码-Base32
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215981">
						<a href="#text_215981">
							base64编码和解码-Base64
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215982">
						<a href="#text_215982">
							有界优先队列-BoundedPriorityQueue
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215983">
						<a href="#text_215983">
							字段验证器-Validator
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215984">
						<a href="#text_215984">
							控制台打印封装-Console
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215985">
						<a href="#text_215985">
							二进码十进数-BCD
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215986">
						<a href="#text_215986">
							HashMap扩展-Dict
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215987">
						<a href="#text_215987">
							字符串格式化-StrFormatter
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215988">
						<a href="#text_215988">
							断言-Assert
						</a>	
					</li>
											</ul>
													<li class="sin_navi_ca" style="width:236px" id="navi_category_52942">
					<a href="#category_52942">
						JavaBean
					</a>
				</li>
							<ul>
																			<li style="width:226px" id="navi_text_215989">
						<a href="#text_215989">
							概述
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215990">
						<a href="#text_215990">
							DynaBean
						</a>	
					</li>
																										<li style="width:226px" id="navi_text_215991">
						<a href="#text_215991">
							表达式解析-BeanResolver
						</a>	
					</li>
											</ul>
								</ul>
																									<li class="sin_navi_ca" style="width: 246px;" id="navi_category_52944">
								<a href="#category_52944">
									配置文件(Hutool-setting)
								</a>
							</li>
										<ul>
																			<li style="width:236px" id="navi_text_215992">
						<a href="#text_215992">
							配置文件模块概述
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_215993">
						<a href="#text_215993">
							设置文件-Setting
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_215994">
						<a href="#text_215994">
							Properties扩展-Props
						</a>	
					</li>
											</ul>
																									<li class="sin_navi_ca" style="width: 246px;" id="navi_category_52945">
								<a href="#category_52945">
									日志(Hutool-log)
								</a>
							</li>
										<ul>
																			<li style="width:236px" id="navi_text_215995">
						<a href="#text_215995">
							Log模块概述
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_215996">
						<a href="#text_215996">
							日志工厂-LogFactory
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_215997">
						<a href="#text_215997">
							静态调用日志-StaticLog
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_215998">
						<a href="#text_215998">
							疑惑解答
						</a>	
					</li>
											</ul>
																									<li class="sin_navi_ca" style="width: 246px;" id="navi_category_52946">
								<a href="#category_52946">
									缓存(Hutool-cache)
								</a>
							</li>
										<ul>
																			<li style="width:236px" id="navi_text_215999">
						<a href="#text_215999">
							概述
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_216306">
						<a href="#text_216306">
							CacheUtil
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_217530">
						<a href="#text_217530">
							FIFOCache
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_217737">
						<a href="#text_217737">
							LFUCache
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_217738">
						<a href="#text_217738">
							LRUCache
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_217741">
						<a href="#text_217741">
							TimedCache
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_217745">
						<a href="#text_217745">
							WeakCache
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_217754">
						<a href="#text_217754">
							FileCache
						</a>	
					</li>
											</ul>
																									<li class="sin_navi_ca" style="width: 246px;" id="navi_category_52947">
								<a href="#category_52947">
									JSON(Hutool-json)
								</a>
							</li>
										<ul>
																			<li style="width:236px" id="navi_text_216000">
						<a href="#text_216000">
							概述
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_216001">
						<a href="#text_216001">
							JSONObject
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_216002">
						<a href="#text_216002">
							JSONArray
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_216003">
						<a href="#text_216003">
							JSONUtil
						</a>	
					</li>
											</ul>
																									<li class="sin_navi_ca" style="width: 246px;" id="navi_category_52948">
								<a href="#category_52948">
									加密解密(Hutool-crypto)
								</a>
							</li>
										<ul>
																			<li style="width:236px" id="navi_text_216004">
						<a href="#text_216004">
							概述
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_216005">
						<a href="#text_216005">
							对称加密-SymmetricCrypto
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_216006">
						<a href="#text_216006">
							非对称加密-AsymmetricCrypto
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_216007">
						<a href="#text_216007">
							摘要加密-Digester和HMac
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_216008">
						<a href="#text_216008">
							加密解密工具-SecureUtil
						</a>	
					</li>
											</ul>
																									<li class="sin_navi_ca" style="width: 246px;" id="navi_category_52949">
								<a href="#category_52949">
									DFA查找(Hutool-dfa)
								</a>
							</li>
										<ul>
																			<li style="width:236px" id="navi_text_216009">
						<a href="#text_216009">
							概述
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_216010">
						<a href="#text_216010">
							DFA查找
						</a>	
					</li>
											</ul>
																									<li class="sin_navi_ca" style="width: 246px;" id="navi_category_52950">
								<a href="#category_52950">
									数据库(Hutool-db)
								</a>
							</li>
										<ul>
																			<li style="width:236px" id="navi_text_216011">
						<a href="#text_216011">
							概述
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_216012">
						<a href="#text_216012">
							数据源工厂-DsFactory
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_216013">
						<a href="#text_216013">
							简单CRUD-SqlRunner
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_216014">
						<a href="#text_216014">
							案例-导出Blob字段图像
						</a>	
					</li>
											</ul>
																									<li class="sin_navi_ca" style="width: 246px;" id="navi_category_52951">
								<a href="#category_52951">
									http客户端(Hutool-http)
								</a>
							</li>
										<ul>
																			<li style="width:236px" id="navi_text_216015">
						<a href="#text_216015">
							概述
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_216016">
						<a href="#text_216016">
							Http客户端工具类-HttpUtil
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_216017">
						<a href="#text_216017">
							Http客户端-HttpRequest
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_216018">
						<a href="#text_216018">
							Http响应封装-HttpResponse
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_216019">
						<a href="#text_216019">
							常用Http状态码-HttpStatus
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_216020">
						<a href="#text_216020">
							HTML工具类-HtmlUtil
						</a>	
					</li>
											</ul>
																									<li class="sin_navi_ca" style="width: 246px;" id="navi_category_52952">
								<a href="#category_52952">
									定时任务(Hutool-cron)
								</a>
							</li>
										<ul>
																			<li style="width:236px" id="navi_text_216021">
						<a href="#text_216021">
							定时任务模块概述
						</a>	
					</li>
											</ul>
																									<li class="sin_navi_ca" style="width: 246px;" id="navi_category_52953">
								<a href="#category_52953">
									扩展(Hutool-extra)
								</a>
							</li>
										<ul>
																			<li style="width:236px" id="navi_text_216022">
						<a href="#text_216022">
							概述
						</a>	
					</li>
											</ul>
																									<li class="sin_navi_ca" style="width: 246px;" id="navi_category_52954">
								<a href="#category_52954">
									布隆过滤(Hutool-bloomFilter)
								</a>
							</li>
										<ul>
																			<li style="width:236px" id="navi_text_216023">
						<a href="#text_216023">
							概述
						</a>	
					</li>
											</ul>
																									<li class="sin_navi_ca" style="width: 246px;" id="navi_category_52955">
								<a href="#category_52955">
									切面(Hutool-aop)
								</a>
							</li>
										<ul>
																			<li style="width:236px" id="navi_text_216024">
						<a href="#text_216024">
							概述
						</a>	
					</li>
											</ul>
																									<li class="sin_navi_ca" style="width: 246px;" id="navi_category_52956">
								<a href="#category_52956">
									脚本(Hutool-script)
								</a>
							</li>
										<ul>
																			<li style="width:236px" id="navi_text_216025">
						<a href="#text_216025">
							概述
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_216026">
						<a href="#text_216026">
							JavaScript引擎
						</a>	
					</li>
											</ul>
																									<li class="sin_navi_ca" style="width: 246px;" id="navi_category_52957">
								<a href="#category_52957">
									系统调用(Hutool-system)
								</a>
							</li>
																																<li class="sin_navi_ca" style="width: 246px;" id="navi_category_52958">
								<a href="#category_52958">
									地理位置(Hutool-geo)
								</a>
							</li>
																																<li class="sin_navi_ca" style="width: 246px;" id="navi_category_52960">
								<a href="#category_52960">
									Office文档操作（Hutool-poi）
								</a>
							</li>
										<ul>
																			<li style="width:236px" id="navi_text_216033">
						<a href="#text_216033">
							概述
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_216035">
						<a href="#text_216035">
							ExcelUtil
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_216037">
						<a href="#text_216037">
							ExcelReader
						</a>	
					</li>
											</ul>
																			</ul>
		</div>
		<div class="sin_content" id="sin_content_id">
																													<h2 id="text_215940" class="sin_target">前言</h2>
<div class="sin_text">
    					<p><img src="https://raw.githubusercontent.com/looly/hutool/master/docs/resources/hutool.jpg" alt="封面"></p>
<h2>全新项目主页：<a href="http://www.hutool.cn/" rel="nofollow">http://www.hutool.cn/</a></h2>
<h2>Hutool交流QQ群：== <a href="http://shang.qq.com/wpa/qunwpa?idkey=382bb37ce779c11da77577f69d92d5171b340e3e7343d5ae0521f237c82c7810" rel="nofollow">537116831</a> ==</h2>
<p>Hutool是一个Java工具集，针对我们项目中util包进行开刀，抽象大量的工具方法，旨在减少项目中工具类的数量，将我们的编码工作专注在业务上。</p>
<h2>功能</h2>
<p>一个Java基础工具类，对文件、流、加密解密、转码、正则、线程、XML等JDK方法进行封装，组成各种Util工具类，同时提供以下组件：<br>* 布隆过滤<br>* 缓存<br>* 克隆接口<br>* 类型转换<br>* 日期处理<br>* 数据库ORM（基于ActiveRecord思想）<br>* 基于DFA有限自动机的多个关键字查找<br>* HTTP客户端<br>* IO和文件<br>* 有用的一些数据结构<br>* 日志<br>* 反射代理类的简化（AOP切面实现）<br>* Setting（一种扩展Properties的配置文件）<br>* System（JVM和系统信息等）<br>* WatchService的封装（文件变动监控）<br>* XXXUtil各种有用的工具类</p>
<h2>简介</h2>
<p><a href="https://github.com/looly/hutool" rel="nofollow">Hutool</a>是一个工具包，我日常写项目的一些积累，希望你看了之后会有所启发或者能给你工作中带来帮助。这个工具包叫做Hutool也有“糊涂”之意，意为很多时候我们并不需要关注细节，专注业务。</p>
<h2>设计哲学</h2>
<h3>1. <strong>方法优先于对象</strong></h3>
<p>在工具类中，往往以静态方法为主。方法集中在一个类中，配合IDE查找使用起来是十分便利的。于是Hutool将JDK中许多的类总结抽象为一个方法，这一原则使用最多的就是流的相关方法，这些方法很好的隐藏了XXXInputStream、XXXReader等的复杂性。</p>
<h3>2. 自动识别优于用户定义</h3>
<p>其实很多时候，有些参数、设置等是没有必要我们自己传入的，完全可以靠逻辑判断自动完成。一个方法很多时候明明只需要传3个参数，我们非要传4个，这多出的一个参数本身就是代码的一种冗余。</p>
<p>这一原则在Hutool的各个角落都有所体现，尤为明显的比如log模块。构建日志对象的时候，很明显类名可以动态获取，何必让使用者再传入呢？再比如在db模块的数据库配置中，数据库驱动命名完全可以根据连接字符串判断出来，何必要让用户传入？这些问题的在Hutool中都有非常好的封装，而这一原则也渐渐变成Hutool哲学的一部分。</p>
<h3>3. 便捷性与灵活性并存</h3>
<p>所谓便捷性，就是我们在调用一个方法的时候参数要尽量少，只传必要参数即可，非必要参数使用默认值即可（想想一个方法一堆参数的时候，调用者晕头转向不知所云）。</p>
<p>所谓灵活性正好与便捷性相反，要让一个方法的参数尽量多，为用户灵活的操作方法提供最大可能性。</p>
<p>这两个原则看似矛盾，其实只是针对不同场景设定的而已，缺一不可。便捷性强调拿来即用，为快速开发提供可能；灵活性强调最大限度调优，为性能调优和扩展提供便利。</p>
<p>这一原则在针对编码问题上体现尤为突出，我们的大部分方法都是默认“UTF-8”编码的，这也是我们推荐的编码方式，推荐大部分项目使用的编码。但是一旦有遗留项目使用了类似“GBK”等编码，没关系，我们提供在相关方法中提供Charset对象参数，可以自定义编码。这样使用这一原则就兼顾了各种项目的情况。</p>
<h3>4. 适配与兼容</h3>
<p>在Hutool中，适配器模式运用特别广泛，log模块适配主流各大框架，db模块适配主流各种连接池和关系数据库。这种适配一是提高灵活性，二是可以很好的兼容各大框架，让Hutool可以在各种复杂项目环境中生存的很好。</p>
<p>适配兼容产生的另一个原则是：**你有我配，你无我有**。说白了就是：如果你项目中有这个框架，我可以完美适配，如果你没有引入任何框架，Hutool自身实现了一些逻辑可以很好的工作。</p>
<h3>5. 可选依赖原则</h3>
<p>在Java项目中依赖常常是个头疼的问题，不同的框架强依赖另一些框架或包，虽然Maven可以很好的处理冲突问题，但是项目底下满满的依赖jar包，是不是无形中拖慢了项目，也增加了复杂性和不确定性？而很多时候，我们是不是只是为了用一个小小的方法，就要引入一个第三方包，谁喜欢这样臃肿的项目？</p>
<p>Hutool中也会有一些依赖，但是全部都是**optional**的，在使用中不会关联依赖，而这些依赖只有在使用者使用到时才会调用，这时可能会报ClassNotFoundException，不用担心，我们自己引入即可。为什么要这样做呢？以VelocityUtil这个工具类为例，使用Velocity的人占比极少，我们不能为了这些用户而强引入Velocity包，而使用这个工具类的人应该明白，我们应该自己引入这个包。</p>
<p>而更多时候，我们需要用到某个方法时，我的做法是将方法拷贝到项目中（Hutool中的方法正在不断积累），类似于Apache Commons中的方法，Hutool中基本都有取代方法，完全不必要引入。</p>
<p>可选依赖原则让我们的项目更加精简，问题也更容易排查。</p>
<h3>6. 无侵入原则</h3>
<p>Hutool始终是一个工具类而不是框架，这意味着它对项目的侵入几乎为零，每个方法都是可被代替的，甚至整个Hutool也是可被替换的。这种无侵入性，让使用者可以更加放心的在项目中引入，也保证了与其它框架完美的兼容。</p>
			</div>

																																		<h2 id="text_215941" class="sin_target">安装</h2>
<div class="sin_text">
    					<h2>安装</h2>
<p>在3.x版本之后，Hutool被拆分为多个模块，项目中可以按照需求添加相应模块，<code>hutool-all</code>包含了所有模块中的工具，如果不清楚需要的东西在哪个模块，项目中引入<code>hutool-all</code>即可。</p>
<h3>子模块</h3> 
<ul> 
 <li>hutool-aop</li> 
 <li>hutool-bloomFilter</li> 
 <li>hutool-cache</li> 
 <li>hutool-core</li> 
 <li>hutool-cron</li> 
 <li>hutool-crypto</li> 
 <li>hutool-db</li> 
 <li>hutool-dfa</li> 
 <li>hutool-extra</li> 
 <li>hutool-http</li> 
 <li>hutool-log</li> 
 <li>hutool-script</li> 
 <li>hutool-setting</li> 
 <li>hutool-system</li> 
 <li>hutool-geo</li> 
 <li>hutool-json</li> 
</ul>
<h3>Maven</h3>
<p>在项目的pom.xml的dependencies中加入以下内容:</p> 
<pre><code>&lt;dependency&gt;
    &lt;groupId&gt;com.xiaoleilu&lt;/groupId&gt;
    &lt;artifactId&gt;hutool-all&lt;/artifactId&gt;
    &lt;version&gt;3.1.0&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<h3>Gradle</h3> 
<pre><code>compile 'com.xiaoleilu:hutool-all:3.1.0'
</code></pre>
<p>注：工具包的**版本**可以通过 <a href="http://search.maven.org/" rel="nofollow">http://search.maven.org/</a> 搜索<code>hutool-all</code>找到项目。</p>
<h3>非Maven项目</h3>
<p>可以从<a href="http://search.maven.org/" rel="nofollow">http://search.maven.org/</a> 搜索<code>hutool-all</code>找到项目，点击对应版本，下面是相应的Jar包，导入即可使用。</p>
<p>如果中央库访问不便，可以访问阿里云的资源：</p>
<p><a href="http://maven.aliyun.com/nexus/content/groups/public/com/xiaoleilu/hutool-all/" rel="nofollow">http://maven.aliyun.com/nexus/content/groups/public/com/xiaoleilu/hutool-all/</a></p>
			</div>

																																		<h2 id="text_215942" class="sin_target">友情开源项目</h2>
<div class="sin_text">
    					<h2>feilong-core</h2>
<p>项目地址：https://github.com/venusdrogon/feilong-core<br>作者：飞天奔月</p>
<p>feilong-core最在开源中国中发现的，与Hutool实现相同的目的，而采用不同的方式。后来与作者@飞天奔月交流甚多。Hutool在feilong-core中也吸取了很多非常好的思想。<br><img src="https://static.oschina.net/uploads/img/201707/11151000_Glow.png" alt="来自飞天奔月的评价" title="来自飞天奔月的评价"></p>
<p>—————————————————————————</p>
<h2>t-io</h2>
<p>项目地址：http://git.oschina.net/tywo45/t-io<br>作者：talent-tan</p>
<p>t-io也就是原来的talent-aio，是一个基于AIO的即时通讯框架，作者@talent-tan也是一个非常热心肠的人，素未谋面却聊的很投缘。也感谢作者把Hutool-core引入t-io作为依赖。<br><img src="https://static.oschina.net/uploads/img/201704/28172303_IYXt.jpg" alt="输入图片说明" title="在这里输入图片标题"></p>
<p>—————————————————————————</p>
<h2>ActFramework</h2>
<p>项目地址：http://git.oschina.net/actframework/actframework<br>作者：greenlaw110</p>
<p>ActFramework是一个抛弃了Servlet规范的MVC框架，这一点让我眼前一亮，而且这个框架背后还有一些作者写的工具类支撑，这些工具类别出心裁（比如用单字母做为类名，或者用$做为类名），让我大开眼界。同时作者也是非常热心的人，我们相互提了一些bug，同时Hutool的一些工具类也参考了作者框架中的一些方法。在此表示感谢。</p>
<p>—————————————————————————</p>
<h2>Voovan</h2>
<p>项目地址：http://git.oschina.net/helyho/Voovan<br>作者：愚民日记</p>
<p>Voovan更像是t-io的竞争者（其实两位作者好的不得了），同时又集成了好多工具类，因此很早以前就被我“盯上”了，后来逐渐通过Hutool群认识作者，相见恨晚。Hutool中也有很多工具方法参考了Voovan中的思想，感谢作者。</p>
			</div>

																										<h2 id="category_52943">核心(Hutool-core)</h2>
																		<h3 id="category_52936">克隆</h3>
																						<h4 id="text_215943" class="sin_target">支持泛型的克隆接口和克隆类</h4>
<div class="sin_text">
    					<h2>我们解决什么问题</h2>
<p>我们知道，JDK中的Cloneable接口只是一个空接口，并没有定义成员，它存在的意义仅仅是指明一个类的实例化对象支持位复制（就是对象克隆），如果不实现这个类，调用对象的clone()方法就会抛出CloneNotSupportedException异常。而且，因为clone()方法在Object对象中，返回值也是Object对象，因此克隆后我们需要自己强转下类型。</p>
<h2>泛型克隆接口</h2>
<p>因此，**com.xiaoleilu.hutool.clone.Cloneable**接口应运而生。此接口定义了一个返回泛型的成员方法，这样，实现此接口后会提示必须实现一个public的clone方法，调用父类clone方法即可：</p> 
<pre><code>/**
 * 猫猫类，使用实现Cloneable方式
 * @author Looly
 *
 */
private static class Cat implements Cloneable&lt;Cat&gt;{
	private String name = "miaomiao";
	private int age = 2;
	
	@Override
	public Cat clone() {
		try {
			return (Cat) super.clone();
		} catch (CloneNotSupportedException e) {
			throw new CloneRuntimeException(e);
		}
	}
}
</code></pre>
<h2>泛型克隆类</h2>
<p>但是实现此接口依旧有不方便之处，就是必须自己实现一个public类型的clone()方法，还要调用父类（Object）的clone方法并处理异常。于是**com.xiaoleilu.hutool.clone.CloneSupport**类产生，这个类帮我们实现了上面的clone方法，因此只要继承此类，不用写任何代码即可使用clone()方法：</p> 
<pre><code>/**
 * 狗狗类，用于继承CloneSupport类
 * @author Looly
 *
 */
private static class Dog extends CloneSupport&lt;Dog&gt;{
	private String name = "wangwang";
	private int age = 3;
}
</code></pre>
<p>当然，使用**CloneSupport**的前提是你没有继承任何的类，谁让Java不支持多重继承呢（你依旧可以让父类实继承这个类，如果可以的话）。如果没办法继承类，那实现**com.xiaoleilu.hutool.clone.Cloneable**也是不错的主意，因此**hutool**提供了这两种方式，任选其一，在便捷和灵活上都提供了支持。</p>
<h2>深克隆</h2>
<p>我们知道实现Cloneable接口后克隆的对象是浅克隆，要想实现深克隆，请使用：</p> 
<pre><code>ObjectUtil.cloneByStream(obj)
</code></pre>
<p>前提是对象必须实现Serializable接口。</p>
<p><strong>ObjectUtil</strong>同样提供一些静态方法：**clone(obj)**、**cloneIfPossible(obj)**用于简化克隆调用，详细的说明请查看核心类的相关文档。</p>
			</div>

																						<h3 id="category_52937">类型转换</h3>
																						<h4 id="text_215944" class="sin_target">类型转换工具类-Convert</h4>
<div class="sin_text">
    					<h2>痛点</h2>
<p>在Java开发中我们要面对各种各样的类型转换问题，尤其是从命令行获取的用户参数、从HttpRequest获取的Parameter等等，这些参数类型多种多样，我们怎么去转换他们呢？常用的办法是先整成String，然后调用XXX.parseXXX方法，还要承受转换失败的风险，不得不加一层try catch，这个小小的过程混迹在业务代码中会显得非常难看和臃肿。</p>
<h2>Convert类</h2>
<p><strong>Convert</strong>类可以说是一个工具方法类，里面封装了针对Java常见类型的转换，用于简化类型转换。**Convert**类中大部分方法为toXXX，参数为Object，可以实现将任意可能的类型转换为指定类型。同时支持第二个参数**defaultValue**用于在转换失败时返回一个默认值。</p>
<h3>Java常见类型转换</h3>
<p>任意类型转为字符串：<br> </p>
<pre><code><br>int a = 1;
long[] b = {1,2,3,4,5};

String aStr = Convert.toStr(a);
Assert.assertEquals("1", aStr);
String bStr = Convert.toStr(b);
Assert.assertEquals("[1, 2, 3, 4, 5]", Convert.toStr(bStr));
</code></pre>
<br>我们可以看到转换工作变得非常容易，而且支持数组的转换字符串。同样，转换数组中元素的类型也同样支持：
<br> 
<pre><code><br>String[] b = { "1", "2", "3", "4" };
		
Integer[] intArray = Convert.toIntArray(false, b);
Assert.assertArrayEquals(intArray, new Integer[]{1,2,3,4});
</code></pre>
<br>为了实现统一性，参数使用了可变参数类型，其实我们需要传入一个Object数组（这里将String数组转为Object数组），转换后为Integer数组。
<p></p> 
<blockquote>
 <h4>注意：</h4>
 <p>由于Java中原始类型众多，Convert转换数组时并没有尝试封装所有的原始类型（例如int、long等），你可以使用**ArrayUtil.wrap**方法将其转换为包装类型数组：<br> </p>
 <pre><code><br>long[] c = {1,2,3,4,5};
Convert.toIntArray(false, (Object[])ArrayUtil.wrap(c));
</code></pre>
 <br>如果考虑性能，请自己实现循环然后使用Convert类转换每一个元素。
 <p></p> 
</blockquote>
<h3>自定义类型转换</h3>
<p>通过<code>Convert.convert(Class&lt;T&gt;, Object)</code>方法可以将任意类型转换为指定类型，Hutool中预定义了许多类型转换，例如转换为URI、URL、Calendar等等，这些类型的转换都依托于<code>ConverterRegistry</code>类。通过这个类和<code>Converter</code>接口，我们可以自定义一些类型转换。详细的使用请参阅“自定义类型转换”一节。</p>
<h3>半角和全角转换</h3>
<p>在很多文本的统一化中这两个方法非常有用，主要对标点符号的全角半角转换。</p>
<p>半角转全角：<br> </p>
<pre><code><br>String a = "123456789";
String sbc = Convert.toSBC(a);
Assert.assertEquals("１２３４５６７８９", sbc);
</code></pre>
<p></p>
<p>全角转半角：<br> </p>
<pre><code><br>String a = "１２３４５６７８９";
String dbc = Convert.toDBC(a);
Assert.assertEquals("123456789", dbc);
</code></pre>
<p></p>
<h3>16进制（Hex）</h3>
<p>在很多加密解密，以及中文字符串传输（比如表单提交）的时候，会用到16进制转换，就是Hex转换，为此Hutool中专门封装了**HexUtil**工具类，考虑到16进制转换也是转换的一部分，因此将其方法也放在Convert类中，便于理解和查找，使用同样非常简单：</p>
<p>转为16进制（Hex）字符串<br> </p>
<pre><code><br>String a = "我是一个小小的可爱的字符串";
String hex = Convert.toHex(a, CharsetUtil.CHARSET_UTF_8);
Assert.assertEquals("e68891e698afe4b880e4b8aae5b08fe5b08fe79a84e58fafe788b1e79a84e5ad97e7aca6e4b8b2", hex);
</code></pre>
<p></p>
<p>将16进制（Hex）字符串转为普通字符串:<br> </p>
<pre><code><br>String a = "我是一个小小的可爱的字符串";
String hex = Convert.toHex(a, CharsetUtil.CHARSET_UTF_8);

String raw = Convert.hexStrToStr(hex, CharsetUtil.CHARSET_UTF_8);
Assert.assertEquals(a, raw);
</code></pre>
<p></p> 
<blockquote>
 <p>因为字符串牵涉到编码问题，因此必须传入编码对象，此处使用UTF-8编码。<br><strong>toHex</strong>方法同样支持传入byte[]，同样也可以使用**hexToBytes**方法将16进制转为byte[]</p> 
</blockquote>
<h3>Unicode和字符串转换</h3>
<p>与16进制类似，Convert类同样可以在字符串和Unicode之间轻松转换：<br> </p>
<pre><code><br>String a = "我是一个小小的可爱的字符串";
		
String unicode = Convert.strToUnicode(a);
Assert.assertEquals("\\u6211\\u662f\\u4e00\\u4e2a\\u5c0f\\u5c0f\\u7684\\u53ef\\u7231\\u7684\\u5b57\\u7b26\\u4e32", unicode);

String raw = Convert.unicodeToStr(unicode);
Assert.assertEquals(raw, a);
</code></pre>
<br>很熟悉吧？如果你在properties文件中写过中文，你会明白这个方法的重要性。
<p></p>
<h3>编码转换</h3>
<p>在接收表单的时候，我们常常被中文乱码所困扰，其实大多数原因是使用了不正确的编码方式解码了数据。于是<code>Convert.convertCharset</code>方法便派上用场了，它可以把乱码转为正确的编码方式：<br> </p>
<pre><code><br>String a = "我不是乱码";
//转换后result为乱码
String result = Convert.convertCharset(a, CharsetUtil.UTF_8, CharsetUtil.ISO_8859_1);
String raw = Convert.convertCharset(result, CharsetUtil.ISO_8859_1, "UTF-8");
Assert.assertEquals(raw, a);
</code></pre>
<br>经过测试，UTF-8编码后用GBK解码再用GBK编码后用UTF-8解码会存在某些中文转换失败的问题。
<p></p>
<h3>时间单位转换</h3>
<p><code>Convert.convertTime</code>方法主要用于转换时长单位，比如一个很大的毫秒，我想获得这个毫秒数对应多少分：<br> </p>
<pre><code><br>long a = 4535345;
long minutes = Convert.convertTime(a, TimeUnit.MILLISECONDS, TimeUnit.MINUTES);
Assert.assertEquals(75, minutes);
</code></pre>
<p></p>
<h3>金额大小写转换</h3>
<p>面对财务类需求，<code>Convert.digitUppercase</code>将金钱数转换为大写形式：<br> </p>
<pre><code><br>double a = 67556.32;
String digitUppercase = Convert.digitUppercase(a);
Assert.assertEquals("陆万柒仟伍佰伍拾陆元叁角贰分", digitUppercase);
</code></pre>
<br>当然，转换为大写只能精确到分（小数点儿后两位），之后的数字会被忽略。
<p></p>
<h3>原始类和包装类转换</h3>
<p>有的时候，我们需要将包装类和原始类相互转换（比如Integer.classs 和 int.class），这时候我们可以：<br> </p>
<pre><code><br>//去包装
Class&lt;?&gt; wrapClass = Integer.class;
Class&lt;?&gt; unWraped = Convert.unWrap(wrapClass);
Assert.assertEquals(int.class, unWraped);

//包装
Class&lt;?&gt; primitiveClass = long.class;
Class&lt;?&gt; wraped = Convert.wrap(primitiveClass);
Assert.assertEquals(Long.class, wraped);
</code></pre>
<p></p>
			</div>

																						<h4 id="text_215945" class="sin_target">自定义类型转换-ConverterRegistry</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p>Hutool中类型转换最早只是一个工具类，叫做“Conver”，对于每一种类型转换都是用一个静态方法表示，但是这种方式有一个潜在问题，那就是扩展性不足，这导致Hutool只能满足部分类型转换的需求。</p>
<h2>解决</h2>
<p>为了解决这些问题，我对Hutool中这个类做了扩展。思想如下：</p> 
<ul> 
 <li><code>Converter</code> 类型转换接口，通过实现这个接口，重写convert方法，以实现不同类型的对象转换</li> 
 <li><code>ConverterRegistry</code> 类型转换登记中心。将各种类型Convert对象放入登记中心，通过<code>convert</code>方法查找目标类型对应的转换器，将被转换对象转换之。在此类中，存放着**默认转换器**和**自定义转换器**，默认转换器是Hutool中预定义的一些转换器，自定义转换器存放用户自定的转换器。</li> 
</ul>
<p>通过这种方式，实现类灵活的类型转换。使用方式如下：<br> </p>
<pre><code><br>int a = 3423;
ConverterRegistry converterRegistry = ConverterRegistry.getInstance();
String result = converterRegistry.convert(String.class, a);
Assert.assertEquals("3423", result);
</code></pre>
<p></p>
<h3>自定义转换</h3>
<p>Hutool的默认转换有时候并不能满足我们自定义对象的一些需求，这时我们可以使用<code>ConverterRegistry.getInstance().putCustom()</code>方法自定义类型转换。</p> 
<ol> 
 <li>自定义转换器</li> 
</ol> 
<pre><code>public static class CustomConverter implements Converter&lt;String&gt;{
	@Override
	public String convert(Object value, String defaultValue) throws IllegalArgumentException {
		return "Custom: " + value.toString();
	}
}
</code></pre> 
<ol> 
 <li>注册转换器</li> 
</ol> 
<pre><code>ConverterRegistry converterRegistry = ConverterRegistry.getInstance();
//此处做为示例自定义String转换，因为Hutool中已经提供String转换，请尽量不要替换
//替换可能引发关联转换异常（例如覆盖String转换会影响全局）
converterRegistry.putCustom(String.class, CustomConverter.class);
</code></pre> 
<ol> 
 <li>执行转换</li> 
</ol> 
<pre><code>int a = 454553;
String result = converterRegistry.convert(String.class, a);
Assert.assertEquals("Custom: 454553", result);
</code></pre> 
<blockquote>
 <p>注意：<br>convert(Class type, Object value, T defaultValue, boolean isCustomFirst)方法的最后一个参数可以选择转换时优先使用自定义转换器还是默认转换器。convert(Class type, Object value, T defaultValue)和convert(Class type, Object value)两个重载方法都是使用自定义转换器优先的模式。</p> 
</blockquote>
<h3><code>ConverterRegistry</code>单例和对象模式</h3>
<p>ConverterRegistry提供一个静态方法getInstance()返回全局单例对象，这也是推荐的使用方式，当然如果想在某个限定范围内自定义转换，可以实例化ConverterRegistry对象。</p>
			</div>

																						<h3 id="category_52938">日期时间</h3>
																						<h4 id="text_215946" class="sin_target">概述</h4>
<div class="sin_text">
    					<p>日期时间包是Hutool的核心包之一，提供针对JDK中Date和Calendar对象的封装，封装对象如下：</p>
<h2>日期时间工具</h2> 
<ul> 
 <li><code>DateUtil</code> 针对日期时间操作提供一系列静态方法</li> 
 <li><code>DateTime</code> 提供类似于Joda-Time中日期时间对象的封装，继承自Date类，并提供更加丰富的对象方法。</li> 
 <li><code>FastDateFormat</code> 提供线程安全的针对Date对象的格式化和日期字符串解析支持。此对象在实际使用中并不需要感知，相关操作已经封装在<code>DateUtil</code>和<code>DateTime</code>的相关方法中。</li> 
 <li><code>DateBetween</code> 计算两个时间间隔的类，除了通过构造新对象使用外，相关操作也已封装在<code>DateUtil</code>和<code>DateTime</code>的相关方法中。</li> 
 <li><code>TimeInterval</code> 一个简单的计时器类，常用于计算某段代码的执行时间，提供包括毫秒、秒、分、时、天、周等各种单位的花费时长计算，对象的静态构造已封装在<code>DateUtil</code>中。</li> 
 <li><code>DatePattern</code> 提供常用的日期格式化模式，包括<code>String</code>类型和<code>FastDateFormat</code>两种类型。</li> 
</ul>
<h2>日期枚举</h2>
<p>考虑到<code>Calendar</code>类中表示时间的字段（field）都是使用<code>int</code>表示，在使用中非常不便，因此针对这些<code>int</code>字段，封装了与之对应的Enum枚举类，这些枚举类在<code>DateUtil</code>和<code>DateTime</code>相关方法中做为参数使用，可以更大限度的缩小参数限定范围。</p>
<p>这些定义的枚举值可以通过<code>getValue()</code>方法获得其与<code>Calendar</code>类对应的int值，通过<code>of(int)</code>方法从<code>Calendar</code>中int值转为枚举对象。</p>
<p>与<code>Calendar</code>对应的这些枚举包括：</p> 
<ul> 
 <li><code>Month</code> 表示月份，与Calendar中的int值一一对应。</li> 
 <li><code>Week</code> 表示周，与Calendar中的int值一一对应</li> 
</ul>
<p>另外，Hutool还定义了**季度**枚举。<code>Season.SPRING</code>为第一季度，表示1~3月。季度的概念并不等同于季节，因为季节与月份并不对应，季度常用于统计概念。</p>
<h2>时间枚举</h2>
<p>时间枚举<code>DateUnit</code>主要表示某个时间单位对应的毫秒数，常用于计算时间差。</p>
<p>例如：<code>DateUnit.MINUTE</code>表示分，也表示一分钟的毫米数，可以通过调用其<code>getMillis()</code>方法获得其毫秒数。</p>
			</div>

																						<h4 id="text_215947" class="sin_target">日期时间工具-DateUtil</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p>考虑到Java本身对日期时间的支持有限，并且Date和Calendar对象的并存导致各种方法使用混乱和复杂，故使用此工具类做了封装。这其中的封装主要是日期和字符串之间的转换，以及提供对日期的定位（一个月前等等）。</p>
<p>对于Date对象，为了便捷，使用了一个DateTime类来代替之，继承自Date对象，主要的便利在于，覆盖了toString()方法，返回yyyy-MM-dd HH:mm:ss形式的字符串，方便在输出时的调用（例如日志记录等），提供了众多便捷的方法对日期对象操作，关于DateTime会在相关章节介绍。</p>
<h2>方法</h2>
<h3>转换</h3>
<h4>Date、long、Calendar之间的相互转换</h4> 
<pre><code>//当前时间
Date date = DateUtil.date();
//当前时间
Date date2 = DateUtil.date(Calendar.getInstance());
//当前时间
Date date3 = DateUtil.date(System.currentTimeMillis());
//当前时间字符串，格式：yyyy-MM-dd HH:mm:ss
String now = DateUtil.now();
//当前日期字符串，格式：yyyy-MM-dd
String today= DateUtil.today();
</code></pre>
<h4>字符串转日期</h4>
<p><code>DateUtil.parse</code>方法会自动识别一些常用格式，包括：<br>1. yyyy-MM-dd HH:mm:ss<br>2. yyyy-MM-dd<br>3. HH:mm:ss<br>4. yyyy-MM-dd HH:mm<br>5. yyyy-MM-dd HH:mm:ss.SSS</p> 
<pre><code>String dateStr = "2017-03-01";
Date date = DateUtil.parse(dateStr);
</code></pre>
<p>我们也可以使用自定义日期格式转化：<br> </p>
<pre><code><br>String dateStr = "2017-03-01";
Date date = DateUtil.parse(dateStr, "yyyy-MM-dd");
</code></pre>
<p></p>
<h4>格式化日期输出</h4> 
<pre><code>String dateStr = "2017-03-01";
Date date = DateUtil.parse(dateStr);

String format = DateUtil.format(date, "yyyy/MM/dd");
Assert.assertEquals("2017/03/01", format);

//常用格式的格式化
String formatDate = DateUtil.formatDate(date);
Assert.assertEquals("2017-03-01", formatDate);
String formatDateTime = DateUtil.formatDateTime(date);
Assert.assertEquals("2017-03-01 00:00:00", formatDateTime);
String formatTime = DateUtil.formatTime(date);
Assert.assertEquals("00:00:00", formatTime);
</code></pre>
<h4>获取Date对象的某个部分</h4> 
<pre><code>Date date = DateUtil.date();
//获得年的部分
DateUtil.year(date);
//获得月份，从1开始计数
DateUtil.month(date);
//获得月份枚举
DateUtil.monthEnum(date);
//.....
</code></pre>
<h4>开始和结束时间</h4>
<p>有的时候我们需要获得每天的开始时间、结束时间，每月的开始和结束时间等等，DateUtil也提供了相关方法：</p> 
<pre><code>String dateStr = "2017-03-01 22:33:23";
Date date = DateUtil.parse(dateStr);

//一天的开始
Date beginOfDay = DateUtil.beginOfDay(date);
Assert.assertEquals("2017-03-01 00:00:00", beginOfDay.toString());
//一天的结束
Date endOfDay = DateUtil.endOfDay(date);
Assert.assertEquals("2017-03-01 23:59:59", endOfDay.toString());
</code></pre>
<h4>日期时间偏移</h4>
<p>日期或时间的偏移指针对某个日期增加或减少分、小时、天等等，达到日期变更的目的。Hutool也针对其做了大量封装</p> 
<pre><code>String dateStr = "2017-03-01 22:33:23";
Date date = DateUtil.parse(dateStr);

Date newDate = DateUtil.offset(date, DateField.DAY_OF_MONTH, 2);
Assert.assertEquals("2017-03-03 22:33:23", newDate.toString());

//常用偏移
DateTime newDate2 = DateUtil.offsetDay(date, 3);
Assert.assertEquals("2017-03-04 22:33:23", newDate2.toString());
//常用偏移
DateTime newDate3 = DateUtil.offsetHour(date, -3);
Assert.assertEquals("2017-03-01 19:33:23", newDate3.toString());
</code></pre>
<p>针对当前时间，提供了简化的偏移方法（例如昨天、上周、上个月等）：<br> </p>
<pre><code><br>//昨天
DateUtil.yesterday()
//明天
DateUtil.tomorrow()
//上周
DateUtil.lastWeek()
//下周
DateUtil.nextWeek()
//上个月
DateUtil.lastMonth()
//下个月
DateUtil.nextMonth()
</code></pre>
<p></p>
<h4>日期时间差</h4>
<p>有时候我们需要计算两个日期之间的时间差（相差天数、相差小时数等等），Hutool将此类方法封装为between方法：</p> 
<pre><code>String dateStr1 = "2017-03-01 22:33:23";
Date date1 = DateUtil.parse(dateStr1);

String dateStr2 = "2017-04-01 23:33:23";
Date date2 = DateUtil.parse(dateStr2);

long betweenDay = DateUtil.between(date1, date2, DateUnit.DAY);
Assert.assertEquals(31, betweenDay);//相差一个月，31天
</code></pre>
<h4>格式化时间差</h4>
<p>有时候我们希望看到易读的时间差，比如XX天XX小时XX分XX秒，此时使用<code>DateUtil.formatBetween</code>方法：</p> 
<pre><code>//Level.MINUTE表示精确到分
String formatBetween = DateUtil.formatBetween(between, Level.MINUTE);
//输出：31天1小时
Console.log(formatBetween);
</code></pre>
<h4>计时器</h4>
<p>计时器用于计算某段代码或过程花费的时间</p> 
<pre><code>TimeInterval timer = DateUtil.timer();

//---------------------------------
//-------这是执行过程
//---------------------------------

timer.interval();//花费毫秒数
timer.intervalRestart();//返回花费时间，并重置开始时间
timer.intervalMinute();//花费分钟数
</code></pre>
<h4>其它</h4> 
<pre><code>//年龄
DateUtil.ageOfNow("1990-01-30");

//是否闰年
DateUtil.isLeapYear(2017);
</code></pre>
			</div>

																						<h4 id="text_215948" class="sin_target">日期时间对象-DateTime</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p>考虑工具类的局限性，在某些情况下使用并不简便，于是<code>DateTime</code>类诞生。<code>DateTime</code>对象充分吸取Joda-Time库的优点，并提供更多的便捷方法，这样我们在开发时不必再单独导入Joda-Time库便可以享受简单快速的日期时间处理过程。</p>
<h2>说明</h2>
<p><strong>DateTime</strong>类继承于java.util.Date类，为Date类扩展了众多简便方法，这些方法多是<code>DateUtil</code>静态方法的对象表现形式，使用DateTime对象可以完全替代开发中Date对象的使用。</p>
<h2>使用</h2>
<h3>新建对象</h3>
<p><code>DateTime</code>对象包含众多的构造方法，构造方法支持的参数有：<br>- Date<br>- Calendar<br>- String(日期字符串，第二个参数是日期格式)<br>- long 毫秒数</p>
<p>构建对象有两种方式：<code>DateTime.of()</code>和<code>new DateTime()</code>：</p> 
<pre><code>Date date = new Date();
		
//new方式创建
DateTime time = new DateTime(date);
Console.log(time);

//of方式创建
DateTime now = DateTime.now();
DateTime dt = DateTime.of(date);
</code></pre>
<h3>使用对象</h3>
<p><code>DateTime</code>的成员方法与<code>DateUtil</code>中的静态方法所对应，因为是成员方法，因此可以使用更少的参数操作日期时间。</p>
<p>示例：获取日期成员（年、月、日等）</p> 
<pre><code>DateTime dateTime = new DateTime("2017-01-05 12:34:23", DatePattern.NORM_DATETIME_FORMAT);
		
//年
int year = dateTime.year();
Assert.assertEquals(2017, year);

//季度（非季节）
Season season = dateTime.seasonEnum();
Assert.assertEquals(Season.SPRING, season);

//月份
Month month = dateTime.monthEnum();
Assert.assertEquals(Month.JANUARY, month);

//日
int day = dateTime.dayOfMonth();
Assert.assertEquals(5, day);
</code></pre>
<p>更多成员方法请参阅API文档。</p>
<h3>对象的可变性</h3>
<p>DateTime对象默认是可变对象（调用offset、setField、setTime方法默认变更自身），但是这种可变性有时候会引起很多问题（例如多个地方共用DateTime对象）。我们可以调用<code>setMutable(false)</code>方法使其变为不可变对象。在不可变模式下，<code>offset</code>、<code>setField</code>方法返回一个新对象，<code>setTime</code>方法抛出异常。</p> 
<pre><code>DateTime dateTime = new DateTime("2017-01-05 12:34:23", DatePattern.NORM_DATETIME_FORMAT);

//默认情况下DateTime为可变对象
DateTime offsite = dateTime.offsite(DateField.YEAR, 0);
Assert.assertTrue(offsite == dateTime);

//设置为不可变对象后变动将返回新对象
dateTime.setMutable(false);
offsite = dateTime.offsite(DateField.YEAR, 0);
Assert.assertFalse(offsite == dateTime);
</code></pre>
<h3>格式化为字符串</h3>
<p>调用<code>toString()</code>方法即可返回格式为<code>yyyy-MM-dd HH:mm:ss</code>的字符串，调用<code>toString(String format)</code>可以返回指定格式的字符串。</p> 
<pre><code>DateTime dateTime = new DateTime("2017-01-05 12:34:23", DatePattern.NORM_DATETIME_FORMAT);
Assert.assertEquals("2017-01-05 12:34:23", dateTime.toString());

String dateStr = dateTime.toString("yyyy/MM/dd");
Assert.assertEquals("2017/01/05", dateStr);
</code></pre>
			</div>

																						<h3 id="category_52939">IO</h3>
																						<h4 id="text_215949" class="sin_target">概述</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p>IO的操作包括**读**和**写**，应用场景包括网络操作和文件操作。IO操作在Java中是一个较为复杂的过程，我们在面对不同的场景时，要选择不同的<code>InputStream</code>和<code>OutputStream</code>实现来完成这些操作。而如果想读写字节流，还需要<code>Reader</code>和<code>Writer</code>的各种实现类。这些繁杂的实现类，一方面给我我们提供了更多的灵活性，另一方面也增加了复杂性。</p>
<h2>封装</h2>
<p>io包的封装主要针对流、文件的读写封装，主要以工具类为主，提供常用功能的封装，这包括：</p> 
<ul> 
 <li><code>IoUtil</code> 流操作工具类</li> 
 <li><code>FileUtil</code> 文件读写和操作的工具类。</li> 
 <li><code>FileTypeUtil</code> 文件类型判断工具类</li> 
 <li><code>WatchMonitor</code> 目录、文件监听，封装了JDK1.7中的WatchService</li> 
 <li><code>ClassPathResource</code>针对ClassPath中资源的访问封装</li> 
 <li><code>FileReader</code> 封装文件读取</li> 
 <li><code>FileWriter</code> 封装文件写入</li> 
</ul>
<h2>流扩展</h2>
<p>除了针对JDK的读写封装外，还针对特定环境和文件扩展了流实现。</p>
<p>包括：<br>- <code>BOMInputStream</code>针对含有BOM头的流读取<br>- <code>FastByteArrayOutputStream</code> 基于快速缓冲FastByteBuffer的OutputStream，随着数据的增长自动扩充缓冲区（from blade）<br>- <code>FastByteBuffer</code> 快速缓冲，将数据存放在缓冲集中，取代以往的单一数组（from blade）</p>
			</div>

																						<h4 id="text_215950" class="sin_target">IO工具类-IoUtil</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p>IO工具类的存在主要针对InputStram、OutputStream、Reader、Writer封装简化，并对NIO相关操作做封装简化。总体来说，Hutool对IO的封装，主要是工具层面，我们努力做到在便捷、性能和灵活之间找到最好的平衡点。</p>
<h2>方法</h2>
<h3>拷贝</h3>
<p>流的读写可以总结为从输入流读取，从输出流写出，这个过程我们定义为**拷贝**。这个是一个基本过程，也是文件、流操作的基础。</p>
<p>以文件流拷贝为例：<br> </p>
<pre><code><br>BufferedInputStream in = FileUtil.getInputStream("d:/test.txt");
BufferedOutputStream out = FileUtil.getOutputStream("d:/test2.txt");
long copySize = IoUtil.copy(in, out, IoUtil.DEFAULT_BUFFER_SIZE);
</code></pre>
<p></p>
<p>copy方法同样针对Reader、Writer、Channel等对象有一些重载方法，并提供可选的缓存大小。默认的，缓存大小为<code>1024</code>个字节，如果拷贝大文件或流数据较大，可以适当调整这个参数。</p>
<p>针对NIO，提供了<code>copyByNIO</code>方法，以便和BIO有所区别。我查阅过一些资料，使用NIO对文件流的操作有一定的提升，我并没有做具体实验。相关测试请参阅博客：<a href="http://www.cnblogs.com/gaopeng527/p/4896783.html" rel="nofollow">http://www.cnblogs.com/gaopeng527/p/4896783.html</a></p>
<h3>Stream转Reader、Writer</h3> 
<ul> 
 <li><code>IoUtil.getReader</code>：将<code>InputStream</code>转为<code>BufferedReader</code>用于读取字符流，它是部分readXXX方法的基础。</li> 
 <li><code>IoUtil.getWriter</code>：将<code>OutputStream</code>转为<code>OutputStreamWriter</code>用于写入字符流，它是部分writeXXX的基础。</li> 
</ul>
<p>本质上这两个方法只是简单new一个新的Reader或者Writer对象，但是封装为工具方法配合IDE的自动提示可以大大减少查阅次数（例如你对BufferedReader、OutputStreamWriter不熟悉，是不需要搜索一下相关类？）</p>
<h3>读取流中的内容</h3>
<p>读取流中的内容总结下来，可以分为read方法和readXXX方法。</p> 
<ol> 
 <li><code>read</code>方法有诸多的重载方法，根据参数不同，可以读取不同对象中的内容，这包括：</li> 
</ol> 
<ul> 
 <li><code>InputStream</code></li> 
 <li><code>Reader</code></li> 
 <li><code>FileChannel</code></li> 
</ul>
<p>这三个重载大部分返回String字符串，为字符流读取提供极大便利。</p> 
<ol> 
 <li><code>readXXX</code>方法主要针对返回值做一些处理，例如：</li> 
</ol> 
<ul> 
 <li><code>readBytes</code> 返回byte数组（读取图片等）</li> 
 <li><code>readHex</code> 读取16进制字符串</li> 
 <li><code>readObj</code> 读取序列化对象（反序列化）</li> 
 <li><code>readLines</code> 按行读取</li> 
</ul> 
<ol> 
 <li><code>toStream</code>方法则是将某些对象转换为流对象，便于在某些情况下操作：</li> 
</ol> 
<ul> 
 <li><code>String</code> 转换为<code>ByteArrayInputStream</code></li> 
 <li><code>File</code> 转换为<code>FileInputStream</code></li> 
</ul>
<h3>写入到流</h3> 
<ul> 
 <li><code>IoUtil.write</code>方法有两个重载方法，一个直接调用<code>OutputStream.write</code>方法，另一个用于将对象转换为字符串（调用toString方法），然后写入到流中。</li> 
 <li><code>IoUtil.writeObjects</code> 用于将可序列化对象序列化后写入到流中。</li> 
</ul>
<p><code>write</code>方法并没有提供writeXXX，需要自己转换为String或byte[]。</p>
<h3>关闭</h3>
<p>对于IO操作来说，使用频率最高（也是最容易被遗忘）的就是<code>close</code>操作，好在Java规范使用了优雅的<code>Closeable</code>接口，这样我们只需简单封装调用此接口的方法即可。</p>
<p>关闭操作会面临两个问题：<br>1. 被关闭对象为空<br>2. 对象关闭失败（或对象已关闭）</p>
<p><code>IoUtil.close</code>方法很好的解决了这两个问题。</p>
<p>在JDK1.7中，提供了<code>AutoCloseable</code>接口，在<code>IoUtil</code>中同样提供相应的重载方法，在使用中并不能感觉到有哪些不同。</p>
			</div>

																						<h4 id="text_215951" class="sin_target">文件工具类-FileUtil</h4>
<div class="sin_text">
    					<h2>简介</h2>
<p>在IO操作中，文件的操作相对来说是比较复杂的，但也是使用频率最高的部分，我们几乎所有的项目中几乎都躺着一个叫做FileUtil或者FileUtils的工具类，我想Hutool应该将这个工具类纳入其中，解决用来解决大部分的文件操作问题。</p>
<p>总体来说，FileUtil类包含以下几类操作工具：<br>1. 文件操作：包括文件目录的新建、删除、复制、移动、改名等<br>2. 文件判断：判断文件或目录是否非空，是否为目录，是否为文件等等。<br>3. 绝对路径：针对ClassPath中的文件转换为绝对路径文件。<br>4. 文件名：主文件名，扩展名的获取<br>5. 读操作：包括类似IoUtil中的getReader、readXXX操作<br>6. 写操作：包括getWriter和writeXXX操作 </p>
<p>在FileUtil中，我努力将方法名与Linux相一致，例如创建文件的方法并不是createFile，而是<code>touch</code>，这种统一对于熟悉Linux的人来说，大大提高了上手速度。当然，如果你不熟悉Linux，那FileUtil工具类的使用则是在帮助你学习Linux命令。这些类Linux命令的方法包括：</p> 
<ul> 
 <li><code>ls</code> 列出目录和文件</li> 
 <li><code>touch</code> 创建文件，如果父目录不存在也自动创建</li> 
 <li><code>mkdir</code> 创建目录，会递归创建每层目录</li> 
 <li><code>del</code> 删除文件或目录（递归删除，不判断是否为空），这个方法相当于Linux的delete命令</li> 
 <li><code>copy</code> 拷贝文件或目录</li> 
</ul>
<p>这些方法提供了人性化的操作，例如<code>touch</code>方法，在创建文件的情况下会自动创建上层目录（我想对于使用者来说这也是大部分情况下的需求），同样<code>mkdir</code>也会创建父目录。</p> 
<blockquote>
 <p>需要注意的是，<code>del</code>方法会删除目录而不判断其是否为空，这一方面方便了使用，另一方面也可能造成一些预想不到的后果（比如拼写错路径而删除不应该删除的目录），所以请谨慎使用此方法。</p> 
</blockquote>
<p>关于FileUtil中更多工具方法，请参阅API文档。</p>
			</div>

																						<h4 id="text_215952" class="sin_target">文件类型判断-FileTypeUtil</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p>在文件上传时，有时候我们需要判断文件类型。但是又不能简单的通过扩展名来判断（防止恶意脚本等通过上传到服务器上），于是我们需要在服务端通过读取文件的首部几个二进制位来判断常用的文件类型。</p>
<h2>使用</h2>
<p>这个工具类使用非常简单，通过调用<code>FileTypeUtil.getType</code>即可判断，这个方法同时提供众多的重载方法，用于读取不同的文件和流。</p> 
<pre><code>File file = FileUtil.file("d:/test.jpg");
String type = FileTypeUtil.getType(file);
//输出 jpg则说明确实为jpg文件
Console.log(type);
</code></pre>
<h2>原理和局限性</h2>
<p>这个类是通过读取文件流中前N个byte值来判断文件类型，在类中我们通过Map形式将常用的文件类型做了映射，这些映射都是网络上搜集而来。也就是说，我们只能识别有限的几种文件类型。但是这些类型已经涵盖了常用的图片、音频、视频、Office文档类型，可以应对大部分的使用场景。</p> 
<blockquote>
 <p>对于某些文本格式的文件我们并不能通过首部byte判断其类型，比如<code>JSON</code>，这类文件本质上是文本文件，我们应该读取其文本内容，通过其语法判断类型。</p> 
</blockquote>
<h2>自定义类型</h2>
<p>为了提高<code>FileTypeUtil</code>的扩展性，我们通过<code>putFileType</code>方法可以自定义文件类型。</p> 
<pre><code>FileTypeUtil.putFileType("ffd8ffe000104a464946", "new_jpg");
</code></pre>
<p>第一个参数是文件流的前N个byte的16进制表示，我们可以读取自定义文件查看，选取一定长度即可(长度越长越精确)，第二个参数就是文件类型，然后使用<code>FileTypeUtil.getType</code>即可。</p>
			</div>

																						<h4 id="text_215953" class="sin_target">文件监听-WatchMonitor</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p><code>WatchMonitor</code>主要针对JDK1.7中<code>WatchService</code>做了封装，针对文件和目录的变动（创建、更新、删除）做一个钩子，在<code>Watcher</code>中定义相应的逻辑来应对这些文件的变化。</p>
<p>在hutool-setting模块，使用WatchMonitor监测配置文件变化，然后自动load到内存中。WatchMonitor的使用可以避免轮询，以事件响应的方式应对文件变化。</p>
<h2>使用</h2>
<p><code>WatchMonitor</code>提供的事件有：</p> 
<ul> 
 <li><code>ENTRY_MODIFY</code> 文件修改的事件</li> 
 <li><code>ENTRY_CREATE</code> 文件或目录创建的事件</li> 
 <li><code>ENTRY_DELETE</code> 文件或目录删除的事件</li> 
 <li><code>OVERFLOW</code> 丢失的事件</li> 
</ul>
<p>这些事件对应<code>StandardWatchEventKinds</code>中的事件。</p>
<p>下面我们介绍WatchMonitor的使用：</p> 
<pre><code>File file = FileUtil.file("example.properties");
WatchMonitor watchMonitor = WatchMonitor.create(file, WatchMonitor.ENTRY_MODIFY);
watchMonitor.setWatcher(new Watcher(){
	
	@Override
	public void onOverflow(WatchEvent&lt;?&gt; event) {
		Console.log("EVENT overflow");
	}
	
	@Override
	public void onModify(WatchEvent&lt;?&gt; event) {
		Console.log("EVENT modify");
	}
	
	@Override
	public void onDelete(WatchEvent&lt;?&gt; event) {
		Console.log("EVENT delete");
	}
	
	@Override
	public void onCreate(WatchEvent&lt;?&gt; event) {
		Console.log("EVENT create");
	}
});
</code></pre>
<p>其实我们不必实现<code>Watcher</code>的所有接口方法，Hutool同时提供了<code>SimpleWatcher</code>类，只需重写对应方法即可。</p>
<p>同样，如果我们想监听所有事件，可以：<br> </p>
<pre><code><br>WatchMonitor.createAll(file, new SimpleWatcher(){
	@Override
	public void onModify(WatchEvent&lt;?&gt; event) {
		Console.log("EVENT modify");
	}
});
</code></pre>
<p></p>
<p><code>createAll</code>方法会创建一个监听所有事件的WatchMonitor，同时在第二个参数中定义Watcher来负责处理这些变动。</p>
			</div>

																						<h4 id="text_215954" class="sin_target">ClassPath资源访问-ClassPathResource</h4>
<div class="sin_text">
    					<h2>什么是ClassPath</h2>
<p>简单说来ClassPath就是查找class文件的路径，在Tomcat等容器下，ClassPath一般是<code>WEB-INF/classes</code>，在普通java程序中，我们可以通过定义<code>-cp</code>或者<code>-classpath</code>参数来定义查找class文件的路径，这些路径就是ClassPath。</p>
<p>为了项目方便，我们定义的配置文件肯定不能使用绝对路径，所以需要使用相对路径，这时候最好的办法就是把配置文件和class文件放在一起，便于查找。</p>
<h2>由来</h2>
<p>在Java编码过程中，我们常常希望读取项目内的配置文件，按照Maven的习惯，这些文件一般放在项目的<code>src/main/resources</code>下，读取的时候使用：</p> 
<pre><code>String path = "config.properties";
InputStream in = this.class.getResource(path).openStream();
</code></pre>
<p>使用当前类来获得资源其实就是使用当前类的类加载器获取资源，最后openStream()方法获取输入流来读取文件流。</p>
<h2>封装</h2>
<p>面对这种复杂的读取操作，我们封装了<code>ClassPathResource</code>类来简化这种资源的读取：</p> 
<pre><code>ClassPathResource resource = new ClassPathResource("test.properties");
Properties properties = new Properties();
properties.load(resource.getStream());

Console.log("Properties: {}", properties);
</code></pre>
<p>这样就大大简化了ClassPath中资源的读取。</p> 
<blockquote>
 <p>Hutool提供针对properties的封装类<code>Props</code>，同时提供更加强大的配置文件Setting类，这两个类已经针对ClassPath做过相应封装，可以以更加便捷的方式读取配置文件。相关文档请参阅Hutool-setting章节</p> 
</blockquote>
			</div>

																						<h4 id="text_215955" class="sin_target">文件读取-FileReader</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p>在<code>FileUtil</code>中本来已经针对文件的读操作做了大量的静态封装，但是根据职责分离原则，我觉得有必要针对文件读取单独封装一个类，这样项目更加清晰。当然，使用FileUtil操作文件是最方便的。</p>
<h2>使用</h2>
<p>在JDK中，同样有一个FileReader类，但是并不如想象中的那样好用，于是Hutool便提供了更加便捷FileReader类。</p> 
<pre><code>//默认UTF-8编码，可以在构造中传入第二个参数做为编码
FileReader fileReader = new FileReader("test.properties");
String result = fileReader.readString();
</code></pre>
<p>FileReader提供了以下方法来快速读取文件内容：</p> 
<ul> 
 <li><code>readBytes</code></li> 
 <li><code>readString</code></li> 
 <li><code>readLines</code></li> 
</ul>
<p>同时，此类还提供了以下方法用于转换为流或者BufferedReader：<br>- <code>getReader</code><br>- <code>getInputStream</code></p>
			</div>

																						<h4 id="text_215956" class="sin_target">文件写入-FileWriter</h4>
<div class="sin_text">
    					<p>相应的，文件读取有了，自然有文件写入类，使用方式与<code>FileReader</code>也类似：</p> 
<pre><code>FileWriter writer = new FileWriter("test.properties");
writer.write("test");
</code></pre>
<p>写入文件分为追加模式和覆盖模式两类，追加模式可以用<code>append</code>方法，覆盖模式可以用<code>write</code>方法，同时也提供了一个write方法，第二个参数是可选覆盖模式。</p>
<p>同样，此类提供了：<br>- <code>getOutputStream</code><br>- <code>getWriter</code><br>- <code>getPrintWriter</code></p>
<p>这些方法用于转换为相应的类提供更加灵活的写入操作。</p>
			</div>

																						<h3 id="category_52940">工具类</h3>
																						<h4 id="text_215957" class="sin_target">概述</h4>
<div class="sin_text">
    					<h2>包含内容</h2>
<p>此包中的工具类为未经过分类的一些工具类，提供一些常用的工具方法。</p>
<p>此包中根据用途归类为XXXUtil，提供大量的工具方法。在工具类中，主要以类方法（static方法）为主，且各个类无法实例化为对象，一个方法是一个独立功能，无相互影响。</p>
<p>关于工具类的说明和使用，请参阅下面的章节。</p>
			</div>

																						<h4 id="text_215958" class="sin_target">数组工具-ArrayUtil</h4>
<div class="sin_text">
    					<h2>介绍</h2>
<p>数组工具中的方法在2.x版本中都在CollectionUtil中存在，3.x版本中拆分出来作为ArrayUtil。数组工具类主要针对原始类型数组和泛型数组相关方案进行封装。</p>
<p>数组工具类主要是解决对象数组（包括包装类型数组）和原始类型数组使用方法不统一的问题。</p>
<h2>方法</h2>
<h3>判空</h3>
<p>数组的判空类似于字符串的判空，标准是<code>null</code>或者数组长度为0，ArrayUtil中封装了针对原始类型和泛型数组的判空和判非空：</p> 
<ol> 
 <li><p>判断空<br> </p><pre><code><br>int[] a = {};
int[] b = null;
ArrayUtil.isEmpty(a);
ArrayUtil.isEmpty(b);
</code></pre><p></p></li> 
 <li><p>判断非空<br> </p><pre><code><br>int[] a = {1,2};
ArrayUtil.isNotEmpty(a);
</code></pre><p></p></li> 
</ol>
<h3>新建泛型数组</h3>
<p><code>Array.newInstance</code>并不支持泛型返回值，在此封装此方法使之支持泛型返回值。</p> 
<pre><code>String[] newArray = ArrayUtil.newArray(String.class, 3);
</code></pre>
<h3>调整大小</h3>
<p>使用 <code>ArrayUtil.resize</code>方法生成一个新的重新设置大小的数组。</p>
<h3>合并数组</h3>
<p><code>ArrayUtil.addAll</code>方法采用可变参数方式，将多个泛型数组合并为一个数组。</p>
<h3>克隆</h3>
<p>数组本身支持clone方法，因此确定为某种类型数组时调用<code>ArrayUtil.clone(T[])</code>,不确定类型的使用<code>ArrayUtil.clone(T)</code>，两种重载方法在实现上有所不同，但是在使用中并不能感知出差别。</p> 
<ol> 
 <li>泛型数组调用原生克隆<br> <pre><code><br>Integer[] b = {1,2,3};
Integer[] cloneB = ArrayUtil.clone(b);
Assert.assertArrayEquals(b, cloneB);
</code></pre></li> 
 <li>非泛型数组（原始类型数组）调用第二种重载方法<br> <pre><code><br>int[] a = {1,2,3};
int[] clone = ArrayUtil.clone(a);
Assert.assertArrayEquals(a, clone);
</code></pre></li> 
</ol>
<h3>有序列表生成</h3>
<p><code>ArrayUtil.range</code>方法有三个重载，这三个重载配合可以实现支持步进的有序数组或者步进为1的有序数组。这种列表生成器在Python中做为语法糖存在。</p>
<h3>拆分数组</h3>
<p><code>ArrayUtil.split</code>方法用于拆分一个byte数组，将byte数组平均分成几等份，常用于消息拆分。</p>
<h3>过滤</h3>
<p><code>ArrayUtil.filter</code>方法用于编辑已有数组元素，只针对泛型数组操作，原始类型数组并未提供。<br>方法中Editor接口用于返回每个元素编辑后的值，返回null此元素将被抛弃。</p>
<p>一个大栗子：过滤数组，只保留偶数<br> </p>
<pre><code><br>Integer[] a = {1,2,3,4,5,6};
Integer[] filter = ArrayUtil.filter(a, new Editor&lt;Integer&gt;(){
	@Override
	public Integer edit(Integer t) {
		return (t % 2 == 0) ? t : null;
	}});
Assert.assertArrayEquals(filter, new Integer[]{2,4,6});
</code></pre>
<p></p>
<h3>zip</h3>
<p><code>ArrayUtil.zip</code>方法传入两个数组，第一个数组为key，第二个数组对应位置为value，此方法在Python中为zip()函数。</p> 
<pre><code>String[] keys = {"a", "b", "c"};
Integer[] values = {1,2,3};
Map&lt;String, Integer&gt; map = ArrayUtil.zip(keys, values, true);

//{a=1, b=2, c=3}
</code></pre>
<h3>是否包含元素</h3>
<p><code>ArrayUtil.contains</code>方法只针对泛型数组，检测指定元素是否在数组中。</p>
<h3>包装和拆包</h3>
<p>在原始类型元素和包装类型中，Java实现了自动包装和拆包，但是相应的数组无法实现，于是便是用<code>ArrayUtil.wrap</code>和<code>ArrayUtil.unwrap</code>对原始类型数组和包装类型数组进行转换。</p>
<h3>判断对象是否为数组</h3>
<p><code>ArrayUtil.isArray</code>方法封装了<code>obj.getClass().isArray()</code>。</p>
<h3>转为字符串</h3> 
<ol> 
 <li><p><code>ArrayUtil.toString</code> 通常原始类型的数组输出为字符串时无法正常显示，于是封装此方法可以完美兼容原始类型数组和包装类型数组的转为字符串操作。</p></li> 
 <li><p><code>ArrayUtil.join</code> 方法使用间隔符将一个数组转为字符串，比如[1,2,3,4]这个数组转为字符串，间隔符使用“-”的话，结果为 1-2-3-4，join方法同样支持泛型数组和原始类型数组。</p></li> 
</ol>
<h3>toArray</h3>
<p><code>ArrayUtil.toArray</code>方法针对ByteBuffer转数组提供便利。</p>
			</div>

																						<h4 id="text_215959" class="sin_target">Bean工具-BeanUtil</h4>
<div class="sin_text">
    					<h2>什么是Bean</h2>
<p>把一个拥有对属性进行set和get方法的类，我们就可以称之为JavaBean。实际上JavaBean就是一个Java类，在这个Java类中就默认形成了一种规则——对属性进行设置和获得。而反之将说Java类就是一个JavaBean，这种说法是错误的，因为一个java类中不一定有对属性的设置和获得的方法（也就是不一定有set和get方法）。</p>
<p>通常Java中对Bean的定义是包含setXXX和getXXX方法的对象，在Hutool中，采取一种简单的判定Bean的方法：是否存在只有一个参数的setXXX方法。</p>
<p>Bean工具类主要是针对这些setXXX和getXXX方法进行操作，比如将Bean对象转为Map等等。</p>
<h2>方法</h2>
<h3>是否为Bean对象</h3>
<p><code>BeanUtil.isBean</code>方法根据是否存在只有一个参数的setXXX方法来判定是否是一个Bean对象。这样的判定方法主要目的是保证至少有一个setXXX方法用于属性注入。</p> 
<pre><code>boolean isBean = BeanUtil.isBean(HashMap.class);//false
</code></pre>
<h3>内省 Introspector</h3>
<p>把一类中需要进行设置和获得的属性访问权限设置为private（私有的）让外部的使用者看不见摸不着，而通过public（共有的）set和get方法来对其属性的值来进行设置和获得，而内部的操作具体是怎样的？外界使用的人不用不知道，这就称为内省。</p>
<p>Hutool中对内省的封装包括：</p> 
<ol> 
 <li><code>BeanUtil.getPropertyDescriptors</code> 获得Bean字段描述数组</li> 
 <li><code>BeanUtil.getFieldNamePropertyDescriptorMap</code> 获得字段名和字段描述Map</li> 
 <li><code>BeanUtil.getPropertyDescriptor</code> 获得Bean类指定属性描述</li> 
</ol>
<h3>Bean属性注入</h3>
<p><code>BeanUtil.fillBean</code>方法是bean注入的核心方法，此方法传入一个ValueProvider接口，通过实现此接口来获得key对应的值。CopyOptions参数则提供一些注入属性的选项。</p>
<p>CopyOptions的配置项包括：<br>1. <code>editable</code> 限制的类或接口，必须为目标对象的实现接口或父类，用于限制拷贝的属性，例如一个类我只想复制其父类的一些属性，就可以将editable设置为父类。<br>2. <code>ignoreNullValue</code> 是否忽略空值，当源对象的值为null时，true:忽略而不注入此值，false: 注入null<br>3. <code>ignoreProperties</code> 忽略的属性列表，设置一个属性列表，不拷贝这些属性值<br>4. <code>ignoreError</code> 是否忽略字段注入错误</p>
<p>可以通过<code>CopyOptions.create()</code>方法创建一个默认的配置项，通过setXXX方法设置每个配置项。</p>
<p>ValueProvider接口需要实现两个方法：<br>1. <code>value</code>方法是通过key和目标类型来从任何地方获取一个值，并转换为目标类型，如果返回值不和目标类型匹配，将会自动调用<code>Convert.convert</code>方法转换。<br>2. <code>containsKey</code>方法主要是检测是否包含指定的key，如果不包含这个key，其对应的属性将会忽略注入。</p>
<p>首先定义一个bean：<br> </p>
<pre><code><br>public class Person{
	private String name;
	private int age;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
}
</code></pre>
<p></p>
<p>然后注入这个bean：<br> </p>
<pre><code><br>Person person = BeanUtil.fillBean(new Person(), new ValueProvider&lt;String&gt;(){

	@Override
	public Object value(String key, Class&lt;?&gt; valueType) {
		switch (key) {
			case "name":
				return "张三";
			case "age":
				return 18;
		}
		return null;
	}

	@Override
	public boolean containsKey(String key) {
		//总是存在key
		return true;
	}
	
}, CopyOptions.create());

Assert.assertEquals(person.getName(), "张三");
Assert.assertEquals(person.getAge(), 18);
</code></pre>
<p></p>
<p>同时，Hutool还提供了<code>BeanUtil.toBean</code>方法，此处并不是传Bean对象，而是Bean类，Hutool会自动调用默认构造方法创建对象。</p>
<p>基于<code>BeanUtil.fillBean</code>方法Hutool还提供了Map对象键值对注入Bean，其方法有：</p> 
<ol> 
 <li><code>BeanUtil.fillBeanWithMap</code></li> 
 <li><code>BeanUtil.fillBeanWithMapIgnoreCase</code></li> 
</ol>
<p>同时提供了map转bean的方法，与fillBean不同的是，此处并不是传Bean对象，而是Bean类，Hutool会自动调用默认构造方法创建对象。当然，前提是Bean类有默认构造方法（空构造），这些方法有：</p> 
<ol> 
 <li><code>BeanUtil.mapToBean</code></li> 
 <li><code>BeanUtil.mapToBeanIgnoreCase</code></li> 
</ol>
<p>在Java Web应用中，我们经常需要将ServletRequest对象中的参数注入bean（http表单数据），BeanUtil类提供了两个便捷方法：</p> 
<ol> 
 <li><code>BeanUtil.fillBeanWithRequestParam</code> 将http表单数据注入Bean对象</li> 
 <li><code>BeanUtil.requestParamToBean</code> 将http表单数据注入新建的Bean对象</li> 
</ol>
<h3>Bean转为Map</h3>
<p><code>BeanUtil.beanToMap</code>方法则是将一个Bean对象转为Map对象。</p>
<h3>Bean转Bean</h3>
<p>Bean之间的转换主要是相同属性的复制，因此方法名为<code>copyProperties</code>。</p>
<p><code>BeanUtil.copyProperties</code>方法同样提供一个<code>CopyOptions</code>参数用于自定义属性复制。</p>
			</div>

																						<h4 id="text_215960" class="sin_target">字符编码工具-CharsetUtil</h4>
<div class="sin_text">
    					<h2>介绍</h2>
<p>CharsetUtil主要针对编码操作做了工具化封装，同时提供了一些常用编码常量。</p>
<h2>常量</h2>
<p>常量在需要编码的地方直接引用，可以很好的提高便利性。</p>
<h3>字符串形式</h3> 
<ol> 
 <li>ISO_8859_1</li> 
 <li>UTF_8</li> 
 <li>GBK</li> 
</ol>
<h3>Charset对象形式</h3> 
<ol> 
 <li>CHARSET_ISO_8859_1</li> 
 <li>CHARSET_UTF_8</li> 
 <li>CHARSET_GBK</li> 
</ol>
<h2>方法</h2>
<h3>编码字符串转为Charset对象</h3>
<p><code>CharsetUtil.charset</code>方法用于将编码形式字符串转为Charset对象。</p>
<h3>转换编码</h3>
<p><code>CharsetUtil.convert</code>方法主要是在两种编码中转换。主要针对因为编码识别错误而导致的乱码问题的一种解决方法。</p>
<h3>系统默认编码</h3>
<p><code>CharsetUtil.defaultCharset</code>方法是<code>Charset.defaultCharset()</code>的封装方法。返回系统编码。<br><code>CharsetUtil.defaultCharsetName</code>方法返回字符串形式的编码类型。</p>
			</div>

																						<h4 id="text_215961" class="sin_target">类工具-ClassUtil</h4>
<div class="sin_text">
    					<h2>类处理工具 <code>ClassUtil</code></h2>
<p>这个工具主要是封装了一些反射的方法，使调用更加方便。而这个类中最有用的方法是<code>scanPackage</code>方法，这个方法会扫描classpath下所有类，这个在Spring中是特性之一，主要为<a href="https://github.com/looly/hulu" rel="nofollow">Hulu</a>框架中类扫描的一个基础。下面介绍下这个类中的方法。</p>
<h3>1.<code>scanPackage</code>方法</h3>
<p>此方法唯一的参数是包的名称，返回结果为此包以及子包下所有的类。方法使用很简单，但是过程复杂一些，包扫面首先会调用 <code>getClassPaths</code>方法获得ClassPath，然后扫描ClassPath，如果是目录，扫描目录下的类文件，或者jar文件。如果是jar包，则直接从jar包中获取类名。这个方法的作用显而易见，就是要找出所有的类，在Spring中用于依赖注入，我在<a href="https://github.com/looly/hulu" rel="nofollow">Hulu</a>中则用于找到Action类。当然，你也可以传一个<code>ClassFilter</code>对象，用于过滤不需要的类。</p>
<h3>2.<code>getMethods</code>方法</h3>
<p>此方法同Class对象的·getMethods·方法，只不过只返回方法的名称（字符串），封装非常简单。</p>
<h3>3.<code>getClassPaths</code>方法</h3>
<p>此方法是获得当前线程的ClassPath，核心是<code>Thread.currentThread().getContextClassLoader().getResources</code>的调用。</p>
<h3>4.<code>getJavaClassPaths</code>方法</h3>
<p>此方法用于获得java的系统变量定义的ClassPath。</p>
<h3>5.<code>parse</code>方法。</h3>
<p>此方法封装了强制类型转换，首先会调用对象本身的<code>cast</code>方法，失败则尝试是否为基本类型（int,long,double,float等），再失败则尝试日期、数字和字节流，总之这是一个包容性较好的类型转换方法，省去我们在不知道类型的情况下多次尝试的繁琐。</p>
<h3>6.<code>parseBasic</code>方法</h3>
<p>此方法被<code>parse</code>方法调用，专门用于将字符集串转换为基本类型的对象(Integer,Double等等)。可以说这是一个一站式的转换方法，JDK的方法名太别扭了，例如你要转换成Long，你得调用<code>Long.parseLong</code>方法，直接<code>Long.parse</code>不就行了……真搞不懂，所以才有了这个方法。</p>
<h3>7.<code>castToPrimitive</code>方法</h3>
<p>这个方法比较别扭，就是把例如Integer类变成int.class，貌似没啥用处，忘了哪里用了，如果你能用到，就太好了。</p>
<h3>8.<code>getClassLoader</code>和<code>getContextClassLoader</code>方法</h3>
<p>后者只是获得当前线程的ClassLoader，前者在获取失败的时候获取<code>ClassUtil</code>这个类的ClassLoader。</p>
<h3>9. <code>newInstance</code>方法</h3>
<p>实例化对象，封装了<code>Class.forName(clazz).newInstance()</code>方法。</p>
<h3>10.<code>cloneObj</code>方法</h3>
<p>克隆对象。对于有些对象没有实现<code>Cloneable</code>接口的对象想克隆下真是费劲，例如封装Redis客户端的时候，配置对象想克隆下基本不可能，于是写了这个方法，原理是使用<code>ObjectOutputStream</code>复制对象流。</p>
			</div>

																						<h4 id="text_215962" class="sin_target">集合工具-CollectionUtil</h4>
<div class="sin_text">
    					<h2>集合工具 <code>CollectionUtil</code></h2>
<p>这个工具主要增加了对数组、集合类的操作。</p>
<h3>1. <code>join</code> 方法</h3>
<p>将集合转换为字符串，这个方法还是挺常用，是<code>StrUtil.split</code>的反方法。这个方法的参数支持各种类型对象的集合，最后连接每个对象时候调用其<code>toString()</code>方法。栗子如下：<br> </p>
<pre><code><br>String[] col= new String[]{a,b,c,d,e};
String str = CollectionUtil.join(col, "#"); //str -&gt; a#b#c#d#e
</code></pre>
<p></p>
<h3>2. <code>sortPageAll</code>、<code>sortPageAll2</code>方法</h3>
<p>这个方法其实是一个真正的组合方法，功能是：将给定的多个集合放到一个列表（<code>List</code>）中，根据给定的<code>Comparator</code>对象排序，然后分页取数据。这个方法非常类似于数据库多表查询后排序分页，这个方法存在的意义也是在此。<code>sortPageAll2</code>功能和<code>sortPageAll</code>的使用方式和结果是 一样的，区别是<code>sortPageAll2</code>使用了<code>BoundedPriorityQueue</code>这个类来存储组合后的列表，不知道哪种性能更好一些，所以就都保留了。使用此方法，栗子如下：<br> </p>
<pre><code><br>//Integer比较器
Comparator&lt;Integer&gt; comparator = new Comparator&lt;Integer&gt;(){
	@Override
	public int compare(Integer o1, Integer o2) {
		return o1.compareTo(o2);
	}
};

//新建三个列表，CollectionUtil.newArrayList方法表示新建ArrayList并填充元素
List&lt;Integer&gt; list1 = CollectionUtil.newArrayList(1, 2, 3);
List&lt;Integer&gt; list2 = CollectionUtil.newArrayList(4, 5, 6);
List&lt;Integer&gt; list3 = CollectionUtil.newArrayList(7, 8, 9);

//参数表示把list1,list2,list3合并并按照从小到大排序后，取0~2个（包括第0个，不包括第2个），结果是[1,2]
@SuppressWarnings("unchecked")
List&lt;Integer&gt; result = CollectionUtil.sortPageAll(0, 2, comparator, list1, list2, list3);
System.out.println(result);     //输出 [1,2]
</code></pre>
<p></p>
<h3>3. <code>sortEntrySetToList</code>方法</h3>
<p>这个方法主要是对<code>Entry&lt;Long, Long&gt;</code>按照Value的值做排序，使用局限性较大，我已经忘记哪里用到过了……</p>
<h3>4. <code>popPart</code>方法</h3>
<p>这个方法传入一个栈对象，然后弹出指定数目的元素对象，弹出是指<code>pop()</code>方法，会从原栈中删掉。</p>
<h3>5.<code>newHashMap</code>、<code>newHashSet</code>、<code>newArrayList</code>方法</h3>
<p>这些方法是新建相应的数据结构，数据结构元素的类型取决于你变量的类型，栗子如下：<br> </p>
<pre><code><br>HashMap&lt;String, String&gt; map = CollectionUtil.newHashMap();
HashSet&lt;String&gt; set = CollectionUtil.newHashSet();
ArrayList&lt;String&gt; list = CollectionUtil.newArrayList();
</code></pre>
<p></p>
<h3>6. <code>append</code>方法</h3>
<p>在给定数组里末尾加一个元素，其实List.add()也是这么实现的，这个方法存在的意义是只有少量的添加元素时使用，因为内部使用了<code>System.arraycopy</code>,每调用一次就要拷贝数组一次。这个方法也是为了在某些只能使用数组的情况下使用，省去了先要转成<code>List</code>，添加元素，再转成Array。</p>
<h3>7. <code>resize</code>方法</h3>
<p>重新调整数据的大小，如果调整后的大小比原来小，截断，如果比原来大，则多出的位置空着。（貌似List在扩充的时候会用到类似的方法）</p>
<h3>8. <code>addAll</code>方法</h3>
<p>将多个数据合并成一个数组</p>
<h5>9. <code>range</code>方法</h5>
<p>这个方法来源于<a href="https://www.python.org/" rel="nofollow">Python</a>的一个语法糖，给定开始和结尾以及步进，就会生成一个等差数列（列表）<br> </p>
<pre><code><br>int[] a1 = CollectionUtil.range(6);       //[0,1,2,3,4,5]
int[] a2 = CollectionUtil.range(4, 7);    //[4,5,6]
int[] a3 = CollectionUtil.range(4, 9, 2); //[4,6,8]
</code></pre>`
<p></p>
<h3>10. <code>sub</code>方法</h3>
<p>对集合切片，其他类型的集合会转换成<code>List</code>，封装<code>List.subList</code>方法，自动修正越界等问题，完全避免<code>IndexOutOfBoundsException</code>异常。</p>
<h3>11. <code>isEmpty</code>、<code>isNotEmpty</code>方法</h3>
<p>判断集合是否为空（包括null和没有元素的集合）。</p>
<h3>12. <code>zip</code>方法</h3>
<p>此方法也是来源于<a href="https://www.python.org/" rel="nofollow">Python</a>的一个语法糖，给定两个集合，然后两个集合中的元素一一对应，成为一个Map。此方法还有一个重载方法，可以传字符，然后给定分分隔符，字符串会被split成列表。栗子：<br> </p>
<pre><code><br>String[] keys = new String[]{"a", "b", "c"};
Integer[] values = new Integer[]{1, 2, 3};
Map&lt;String, Integer&gt; map = CollectionUtil.zip(keys,values);
System.out.println(map);    // {b=2, c=3, a=1}

String a = "a,b,c";
String b = "1,2,3";
Map&lt;String, String&gt; map2 = CollectionUtil.zip(a,b, ",");
System.out.println(map2);   // {b=2, c=3, a=1}
</code></pre>
<p></p>
<h3>13. <code>filter</code>方法</h3>
<p>此方法可以过滤map，排除不需要的key。栗子：<br> </p>
<pre><code><br>@Test
    public void CollectionUtil_Filter() {
        Map&lt;String, Object&gt; m = new HashMap&lt;String, Object&gt;() {{
            put("k1", "v1");
            put("k2", "v2");
            put("k3", "v3");
        }};
        String[] inc = {"k1", "k3"};//需要的key
        List&lt;String&gt; incList = Arrays.asList(inc);
        m = CollectionUtil.filter(m, new Editor&lt;Map.Entry&lt;String, Object&gt;&gt;() {
            @Override
            public Map.Entry&lt;String, Object&gt; edit(Map.Entry&lt;String, Object&gt; stringObjectEntry) {
                if (incList.contains(stringObjectEntry.getKey())) {
                    return stringObjectEntry;
                }
                return null;
            }
        });
        log.info("{}", m);
    }
</code></pre>
<br>结果
<br> 
<pre><code><br>{k3=v3, k1=v1}
</code></pre>
<p></p>
			</div>

																						<h4 id="text_215963" class="sin_target">Escape工具-EscapeUtil</h4>
<div class="sin_text">
    					<h2>介绍</h2>
<p>转义和反转义工具类Escape / Unescape。escape采用ISO Latin字符集对指定的字符串进行编码。所有的空格符、标点符号、特殊字符以及其他非ASCII字符都将被转化成%xx格式的字符编码(xx等于该字符在字符集表里面的编码的16进制数字)。</p>
<p>此类中的方法对应Javascript中的<code>escape()</code>函数和<code>unescape()</code>函数。</p>
<h2>方法</h2> 
<ol> 
 <li><p><code>EscapeUtil.escape</code> Escape编码（Unicode），该方法不会对 ASCII 字母和数字进行编码，也不会对下面这些 ASCII 标点符号进行编码： * @ - _ + . / 。其他所有的字符都会被转义序列替换。</p></li> 
 <li><p><code>EscapeUtil.unescape</code> Escape解码。</p></li> 
 <li><p><code>EscapeUtil.safeUnescape</code> 安全的unescape文本，当文本不是被escape的时候，返回原文。</p></li> 
</ol>
			</div>

																						<h4 id="text_215964" class="sin_target">Hash算法-HashUtil</h4>
<div class="sin_text">
    					<h2>介绍</h2>
<p><code>HashUtil</code>其实是一个hash算法的集合，此工具类中融合了各种hash算法。</p>
<h2>方法</h2>
<p>这些算法包括：</p> 
<ol> 
 <li><code>additiveHash</code> 加法hash</li> 
 <li><code>rotatingHash</code> 旋转hash</li> 
 <li><code>oneByOneHash</code> 一次一个hash</li> 
 <li><code>bernstein</code> Bernstein’s hash</li> 
 <li><code>universal</code> Universal Hashing</li> 
 <li><code>zobrist</code> Zobrist Hashing</li> 
 <li><code>fnvHash</code> 改进的32位FNV算法1</li> 
 <li><code>intHash</code> Thomas Wang的算法，整数hash</li> 
 <li><code>rsHash</code> RS算法hash</li> 
 <li><code>jsHash</code> JS算法</li> 
 <li><code>pjwHash</code> PJW算法</li> 
 <li><code>elfHash</code> ELF算法</li> 
 <li><code>bkdrHash</code> BKDR算法</li> 
 <li><code>sdbmHash</code> SDBM算法</li> 
 <li><code>djbHash</code> DJB算法</li> 
 <li><code>dekHash</code> DEK算法</li> 
 <li><code>apHash</code> AP算法</li> 
 <li><code>tianlHash</code> TianL Hash算法</li> 
 <li><code>javaDefaultHash</code> JAVA自己带的算法</li> 
 <li><code>mixHash</code> 混合hash算法，输出64位的值</li> 
</ol>
			</div>

																						<h4 id="text_215965" class="sin_target">16进制工具-HexUtil</h4>
<div class="sin_text">
    					<h2>介绍</h2>
<p>十六进制（简写为hex或下标16）在数学中是一种逢16进1的进位制，一般用数字0到9和字母A到F表示（其中:A~F即10~15）。例如十进制数57，在二进制写作111001，在16进制写作39。</p>
<p>像java,c这样的语言为了区分十六进制和十进制数值,会在十六进制数的前面加上 0x,比如0x20是十进制的32,而不是十进制的20。<code>HexUtil</code>就是将字符串或byte数组与16进制表示转换的工具类。</p>
<h2>用于</h2>
<p>16进制一般针对无法显示的一些二进制进行显示，常用于：<br>1、图片的字符串表现形式<br>2、加密解密<br>3、编码转换</p>
<h2>使用</h2>
<p><code>HexUtil</code>主要以<code>encodeHex</code>和<code>decodeHex</code>两个方法为核心，提供一些针对字符串的重载方法。</p> 
<pre><code>String str = "我是一个字符串";

String hex = HexUtil.encodeHexStr(str, CharsetUtil.CHARSET_UTF_8);

//hex是：
//e68891e698afe4b880e4b8aae5ad97e7aca6e4b8b2

String decodedStr = HexUtil.decodeHexStr(hex);

//解码后与str相同
</code></pre>
			</div>

																						<h4 id="text_215966" class="sin_target">身份证工具-IdcardUtil</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p>在日常开发中，我们对身份证的验证主要是正则方式（位数，数字范围等），但是中国身份证，尤其18位身份证每一位都有严格规定，并且最后一位为校验位。而我们在实际应用中，针对身份证的验证理应严格至此。于是<code>IdcardUtil</code>应运而生。</p> 
<blockquote>
 <p><code>IdcardUtil</code>从3.0.4版本起加入Hutool工具家族，请升级至此版本以上可使用。</p> 
</blockquote>
<h2>介绍</h2>
<p><code>IdcardUtil</code>现在支持大陆15位、18位身份证，港澳台10位身份证。</p>
<p>工具中主要的方法包括：<br>1. <code>isValidCard</code> 验证身份证是否合法<br>2. <code>convert15To18</code> 身份证15位转18位<br>3. <code>getBirthByIdCard</code> 获取生日<br>4. <code>getAgeByIdCard</code> 获取年龄<br>5. <code>getYearByIdCard</code> 获取生日年<br>6. <code>getMonthByIdCard</code> 获取生日月<br>7. <code>getDayByIdCard</code> 获取生日天<br>8. <code>getGenderByIdCard</code> 获取性别<br>9. <code>getProvinceByIdCard</code> 获取省份</p>
<h2>使用</h2> 
<pre><code>String ID_18 = "321083197812162119";
String ID_15 = "150102880730303";

//是否有效
boolean valid = IdcardUtil.isValidCard(ID_18);
boolean valid15 = IdcardUtil.isValidCard(ID_15);

//转换
String convert15To18 = IdcardUtil.convert15To18(ID_15);
Assert.assertEquals(convert15To18, "150102198807303035");

//年龄
DateTime date = DateUtil.parse("2017-04-10");
		
int age = IdcardUtil.getAgeByIdCard(ID_18, date);
Assert.assertEquals(age, 38);

int age2 = IdcardUtil.getAgeByIdCard(ID_15, date);
Assert.assertEquals(age2, 28);

//生日
String birth = IdcardUtil.getBirthByIdCard(ID_18);
Assert.assertEquals(birth, "19781216");

String birth2 = IdcardUtil.getBirthByIdCard(ID_15);
Assert.assertEquals(birth2, "19880730");

//省份
String province = IdcardUtil.getProvinceByIdCard(ID_18);
Assert.assertEquals(province, "江苏");

String province2 = IdcardUtil.getProvinceByIdCard(ID_15);
Assert.assertEquals(province2, "内蒙古");
</code></pre> 
<blockquote>
 <p><strong>声明</strong><br>以上两个身份证号码为随机编造的，如有雷同，纯属巧合。</p> 
</blockquote>
			</div>

																						<h4 id="text_215967" class="sin_target">图片工具-ImageUtil</h4>
<div class="sin_text">
    					<h2>介绍</h2>
<p>针对awt中图片处理进行封装，这些封装包括：缩放、裁剪、转为黑白、加水印等操作。</p>
<h2>方法介绍</h2> 
<ol> 
 <li><code>scale</code> 缩放图片，提供两种重载方法：其中一个是按照长宽缩放，另一种是按照比例缩放。</li> 
 <li><code>cut</code> 剪裁图片</li> 
 <li><code>cutByRowsAndCols</code> 按照行列剪裁（将图片分为20行和20列）</li> 
 <li><code>convert</code> 图片类型转换，支持GIF-&gt;JPG、GIF-&gt;PNG、PNG-&gt;JPG、PNG-&gt;GIF(X)、BMP-&gt;PNG等</li> 
 <li><code>gray</code> 彩色转为黑白</li> 
 <li><code>pressText</code> 添加文字水印</li> 
 <li><code>pressImage</code> 添加图片水印</li> 
</ol>
			</div>

																						<h4 id="text_215968" class="sin_target">网络工具-NetUtil</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p>在日常开发中，网络连接这块儿必不可少。日常用到的一些功能,隐藏掉部分IP地址、绝对相对路径的转换等等。</p>
<h2>介绍</h2>
<p><code>NetUtil</code> 工具中主要的方法包括：<br>1. <code>longToIpv4</code> 根据long值获取ip v4地址<br>2. <code>ipv4ToLong</code> 根据ip地址计算出long型的数据<br>3. <code>isUsableLocalPort</code> 检测本地端口可用性<br>4. <code>isValidPort</code> 是否为有效的端口<br>5. <code>isInnerIP</code> 判定是否为内网IP<br>6. <code>localIpv4s</code> 获得本机的IP地址列表<br>7. <code>toAbsoluteUrl</code> 相对URL转换为绝对URL<br>8. <code>hideIpPart</code> 隐藏掉IP地址的最后一部分为 * 代替<br>9. <code>buildInetSocketAddress</code> 构建InetSocketAddress<br>10. <code>getIpByHost</code> 通过域名得到IP<br>11. <code>isInner</code> 指定IP的long是否在指定范围内</p>
<h2>使用</h2> 
<pre><code>String ip= "127.0.0.1";
long iplong = 2130706433L;

//根据long值获取ip v4地址
String ip= NetUtil.longToIpv4(iplong);


//根据ip地址计算出long型的数据
long ip= NetUtil.ipv4ToLong(ip);

//检测本地端口可用性
boolean result= NetUtil.isUsableLocalPort(6379);

//是否为有效的端口
boolean result= NetUtil.isValidPort(6379);

//隐藏掉IP地址
 String result =NetUtil.hideIpPart(ip);
</code></pre> 
<blockquote>
 <p><strong>声明</strong><br>以上抛砖引玉，更多精彩请翻看NetUtil源码，也期待您一起完善NetUtil功能。</p> 
</blockquote>
			</div>

																						<h4 id="text_215969" class="sin_target">数字工具-NumberUtil</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p>数字工具针对数学运算做工具性封装</p>
<h2>使用</h2>
<h3>加减乘除</h3> 
<ul> 
 <li><code>NumberUtil.add</code> 针对double类型做加法</li> 
 <li><code>NumberUtil.sub</code> 针对double类型做减法</li> 
 <li><code>NumberUtil.mul</code> 针对double类型做乘法</li> 
 <li><code>NumberUtil.div</code> 针对double类型做除法，并提供重载方法用于规定除不尽的情况下保留小数位数和舍弃方式。</li> 
</ul>
<p>以上四种运算都会将double转为BigDecimal后计算，解决float和double类型无法进行精确计算的问题。这些方法常用于商业计算。</p>
<h3>保留小数</h3>
<p>保留小数的方法主要有两种：</p> 
<ul> 
 <li><code>NumberUtil.round</code> 方法主要封装BigDecimal中的方法来保留小数，返回double，这个方法更加灵活，可以选择四舍五入或者全部舍弃等模式。</li> 
</ul> 
<pre><code>double te1=123456.123456;
double te2=123456.128456;
Console.log(round(te1,4));//结果:123456.12
Console.log(round(te2,4));//结果:123456.13
</code></pre> 
<ul> 
 <li><code>NumberUtil.roundStr</code> 方法主要封装<code>String.format</code>方法,舍弃方式采用四舍五入。</li> 
</ul> 
<pre><code>double te1=123456.123456;
double te2=123456.128456;
Console.log(roundStr(te1,2));//结果:123456.12
Console.log(roundStr(te2,2));//结果:123456.13
</code></pre>
<h3>decimalFormat</h3>
<p>针对 <code>DecimalFormat.format</code>进行简单封装。按照固定格式对double或long类型的数字做格式化操作。</p> 
<pre><code>long c=299792458;//光速
String format = NumberUtil.decimalFormat(",###", c);//299,792,458
</code></pre>
<p>格式中主要以 # 和 0 两种占位符号来指定数字长度。0 表示如果位数不足则以 0 填充，# 表示只要有可能就把数字拉上这个位置。</p> 
<ul> 
 <li>0 -&gt; 取一位整数</li> 
 <li>0.00 -&gt; 取一位整数和两位小数</li> 
 <li>00.000 -&gt; 取两位整数和三位小数</li> 
 <li># -&gt; 取所有整数部分</li> 
 <li>#.##% -&gt; 以百分比方式计数，并取两位小数</li> 
 <li>#.#####E0 -&gt; 显示为科学计数法，并取五位小数</li> 
 <li>,### -&gt; 每三位以逗号进行分隔，例如：299,792,458</li> 
 <li>光速大小为每秒,###米 -&gt; 将格式嵌入文本</li> 
</ul>
<p>关于格式的更多说明，请参阅：<a href="http://blog.csdn.net/evangel_z/article/details/7624503" rel="nofollow">Java DecimalFormat的主要功能及使用方法</a></p>
<h3>是否为数字</h3> 
<ul> 
 <li><code>NumberUtil.isNumber</code> 是否为数字</li> 
 <li><code>NumberUtil.isInteger</code> 是否为整数</li> 
 <li><code>NumberUtil.isDouble</code> 是否为浮点数</li> 
 <li><code>NumberUtil.isPrimes</code> 是否为质数</li> 
</ul>
<h3>随机数</h3> 
<ul> 
 <li><code>NumberUtil.generateRandomNumber</code> 生成不重复随机数 根据给定的最小数字和最大数字，以及随机数的个数，产生指定的不重复的数组。</li> 
 <li><code>NumberUtil.generateBySet</code> 生成不重复随机数 根据给定的最小数字和最大数字，以及随机数的个数，产生指定的不重复的数组。</li> 
</ul>
<h3>整数列表</h3>
<p><code>NumberUtil.range</code> 方法根据范围和步进，生成一个有序整数列表。<br><code>NumberUtil.appendRange</code> 将给定范围内的整数添加到已有集合中</p>
<h3>其它</h3> 
<ul> 
 <li><code>NumberUtil.factorial</code> 阶乘</li> 
 <li><code>NumberUtil.sqrt</code> 平方根</li> 
 <li><code>NumberUtil.divisor</code> 最大公约数</li> 
 <li><code>NumberUtil.multiple</code> 最小公倍数</li> 
 <li><code>NumberUtil.getBinaryStr</code> 获得数字对应的二进制字符串</li> 
 <li><code>NumberUtil.binaryToInt</code> 二进制转int</li> 
 <li><code>NumberUtil.binaryToLong</code> 二进制转long</li> 
 <li><code>NumberUtil.compare</code> 比较两个值的大小</li> 
 <li><code>NumberUtil.toStr</code> 数字转字符串，自动并去除尾小数点儿后多余的0</li> 
</ul>
			</div>

																						<h4 id="text_215970" class="sin_target">分页工具-PageUtil</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p>分页工具类并不是数据库分页的封装，而是分页方式的转换。在我们手动分页的时候，常常使用页码+每页个数的方式，但是有些数据库需要使用开始位置和结束位置来表示。很多时候这种转换容易出错（边界问题），于是封装了PageUtil工具类。</p>
<h2>使用</h2>
<h3>transToStartEnd</h3>
<p>将页数和每页条目数转换为开始位置和结束位置。<br>此方法用于不包括结束位置的分页方法。</p>
<p>例如：<br>- 页码：1，每页10 -&gt; [0, 10]<br>- 页码：2，每页10 -&gt; [10, 20]</p> 
<pre><code>int[] startEnd1 = PageUtil.transToStartEnd(1, 10);//[0, 10]
int[] startEnd2 = PageUtil.transToStartEnd(2, 10);//[10, 20]
</code></pre> 
<blockquote>
 <p>方法中，页码从1开始，位置从0开始</p> 
</blockquote>
<h3>totalPage</h3>
<p>根据总数计算总页数</p> 
<pre><code>int totalPage = PageUtil.totalPage(20, 3);//7
</code></pre>
			</div>

																						<h4 id="text_215971" class="sin_target">对象工具-ObjectUtil</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p>在我们的日常使用中，有些方法是针对Object通用的，这些方法不区分何种对象，针对这些方法，Hutool封装为<code>ObjectUtil</code>。</p>
<h2>方法</h2>
<h3><code>ObjectUtil.equal</code></h3>
<p>比较两个对象是否相等，相等需满足以下连个条件之一：<br>1. obj1 == null &amp;&amp; obj2 == null<br>2. obj1.equals(obj2)</p>
<h3><code>ObjectUtil.length</code></h3>
<p>计算对象长度，如果是字符串调用其length方法，集合类调用其size方法，数组调用其length属性，其他可遍历对象遍历计算长度。</p>
<p>支持的类型包括：<br>- CharSequence<br>- Collection<br>- Map<br>- Iterator<br>- Enumeration<br>- Array</p>
<h3><code>ObjectUtil.contains</code></h3>
<p>对象中是否包含元素。</p>
<p>支持的对象类型包括：<br>- String<br>- Collection<br>- Map<br>- Iterator<br>- Enumeration<br>- Array</p>
<h3>判断是否为null</h3> 
<ul> 
 <li><code>ObjectUtil.isNull</code></li> 
 <li><code>ObjectUtil.isNotNull</code></li> 
</ul>
<h3>克隆</h3> 
<ul> 
 <li><p><code>ObjectUtil.clone</code> 克隆对象，如果对象实现Cloneable接口，调用其clone方法，如果实现Serializable接口，执行深度克隆，否则返回<code>null</code>。</p></li> 
 <li><p><code>ObjectUtil.cloneIfPossible</code> 返回克隆后的对象，如果克隆失败，返回原对象</p></li> 
 <li><p><code>ObjectUtil.cloneByStream</code> 序列化后拷贝流的方式克隆，对象必须实现Serializable接口</p></li> 
</ul>
<h3>序列化和反序列化</h3> 
<ul> 
 <li><code>serialize</code> 序列化，调用JDK序列化</li> 
 <li><code>unserialize</code> 反序列化，调用JDK</li> 
</ul>
<h3>判断基本类型</h3>
<p><code>ObjectUtil.isBasicType</code> 判断是否为基本类型，包括包装类型和非包装类型。</p>
			</div>

																						<h4 id="text_215972" class="sin_target">随机工具-RandomUtil</h4>
<div class="sin_text">
    					<h2>说明</h2>
<p><code>RandomUtil</code>主要针对JDK中<code>Random</code>对象做封装，严格来说，Java产生的随机数都是伪随机数，因此Hutool封装后产生的随机结果也是伪随机结果。不过这种随机结果对于大多数情况已经够用。</p>
<h2>使用</h2> 
<ul> 
 <li><code>RandomUtil.randomInt</code> 获得指定范围内的随机数</li> 
 <li><code>RandomUtil.randomBytes</code> 随机bytes</li> 
 <li><code>RandomUtil.randomEle</code> 随机获得列表中的元素</li> 
 <li><code>RandomUtil.randomEleSet</code> 随机获得列表中的一定量的不重复元素，返回Set</li> 
 <li><code>RandomUtil.randomString</code> 获得一个随机的字符串（只包含数字和字符）</li> 
 <li><code>RandomUtil.randomNumbers</code> 获得一个只包含数字的字符串</li> 
 <li><code>RandomUtil.randomUUID</code> 随机UUID</li> 
</ul>
			</div>

																						<h4 id="text_215973" class="sin_target">字符串工具-StrUtil</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p>这个工具的用处类似于<a href="http://commons.apache.org/" rel="nofollow">Apache Commons Lang</a>中的<code>StringUtil</code>，之所以使用<code>StrUtil</code>而不是使用<code>StringUtil</code>是因为前者更短，而且<code>Str</code>这个简写我想已经深入人心了，大家都知道是字符串的意思。常用的方法例如<code>isBlank</code>、<code>isNotBlank</code>、<code>isEmpty</code>、<code>isNotEmpty</code>这些我就不做介绍了，判断字符串是否为空，下面我说几个比较好用的功能。</p>
<h2>方法</h2>
<h3>1. <code>hasBlank</code>、<code>hasEmpty</code>方法</h3>
<p>就是给定一些字符串，如果一旦有空的就返回true，常用于判断好多字段是否有空的（例如web表单数据）。</p>
<p><strong>这两个方法的区别是<code>hasEmpty</code>只判断是否为null或者空字符串（""），<code>hasBlank</code>则会把不可见字符也算做空，<code>isEmpty</code>和<code>isBlank</code>同理。</strong></p>
<h3>2. <code>removePrefix</code>、<code>removeSuffix</code>方法</h3>
<p>这两个是去掉字符串的前缀后缀的，例如去个文件名的扩展名啥。</p> 
<pre><code>String fileName = StrUtil.removeSuffix("pretty_girl.jpg", ".jpg")  //fileName -&gt; pretty_girl
</code></pre>
<p>还有忽略大小写的<code>removePrefixIgnoreCase</code>和<code>removeSuffixIgnoreCase</code>都比较实用。</p>
<h3>3. <code>sub</code>方法</h3>
<p>不得不提一下这个方法，有人说String有了subString你还写它干啥，我想说subString方法越界啥的都会报异常，你还得自己判断，难受死了，我把各种情况判断都加进来了，而且index的位置还支付负数哦，-1表示最后一个字符（这个思想来自于<a href="https://www.python.org/" rel="nofollow">Python</a>，如果学过<a href="https://www.python.org/" rel="nofollow">Python</a>的应该会很喜欢的），还有就是如果不小心把第一个位置和第二个位置搞反了，也会自动修正（例如想截取第4个和第2个字符之间的部分也是可以的哦~）<br>举个栗子</p> 
<pre><code>String str = "abcdefgh";
String strSub1 = StrUtil.sub(str, 2, 3); //strSub1 -&gt; c
String strSub2 = StrUtil.sub(str, 2, -3); //strSub2 -&gt; cde
String strSub3 = StrUtil.sub(str, 3, 2); //strSub2 -&gt; c
</code></pre>
<h3>4. <code>str</code>、<code>bytes</code>方法</h3>
<p>好吧，我承认把<code>String.getByte(String charsetName)</code>方法封装在这里了，原生的<code>String.getByte()</code>这个方法太坑了，使用系统编码，经常会有人跳进来导致乱码问题，所以我就加了这两个方法强制指定字符集了，包了个try抛出一个运行时异常，省的我得在我业务代码里处理那个恶心的<code>UnsupportedEncodingException</code>。</p>
<h3>5. format方法</h3>
<p>我会告诉你这是我最引以为豪的方法吗？灵感来自slf4j，可以使用字符串模板代替字符串拼接，我也自己实现了一个，而且变量的标识符都一样，神马叫无缝兼容~~来，上栗子（吃多了上火吧……）<br> </p>
<pre><code><br>String template = "{}爱{}，就像老鼠爱大米";
String str = StrUtil.format(template, "我", "你"); //str -&gt; 我爱你，就像老鼠爱大米
</code></pre>`
<br>参数我定义成了Object类型，如果传别的类型的也可以，会自动调用toString()方法的。
<p></p>
<h3>6. 定义的一些常量</h3>
<p>为了方便，我定义了一些比较常见的字符串常量在里面，像点、空串、换行符等等，还有HTML中的一些转移字符。</p>
<p>更多方法请参阅API文档。</p>
			</div>

																						<h4 id="text_215974" class="sin_target">正则工具-ReUtil</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p>在文本处理中，正则表达式几乎是全能的，但是Java的正则表达式有时候处理一些事情还是有些繁琐，所以我封装了部分常用功能。就比如说我要匹配一段文本中的某些部分，我们需要这样做：</p> 
<pre><code>Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
Matcher matcher = pattern.matcher(content);
if (matcher.find()) {
    String result= matcher.group();
}
</code></pre>
<p>其中牵涉到多个对象，想用的时候真心记不住。好吧，既然功能如此常用，我就封装一下：</p> 
<pre><code>/**
* 获得匹配的字符串
* 
* @param pattern 编译后的正则模式
* @param content 被匹配的内容
* @param groupIndex 匹配正则的分组序号
* @return 匹配后得到的字符串，未匹配返回null
*/
public static String get(Pattern pattern, String content, int groupIndex) {
    Matcher matcher = pattern.matcher(content);
    if (matcher.find()) {
        return matcher.group(groupIndex);
    }
    return null;
}

/**
* 获得匹配的字符串
* 
* @param regex 匹配的正则
* @param content 被匹配的内容
* @param groupIndex 匹配正则的分组序号
* @return 匹配后得到的字符串，未匹配返回null
*/
public static String get(String regex, String content, int groupIndex) {
    Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
    return get(pattern, content, groupIndex);
}
</code></pre>
<h2>使用</h2>
<h3>ReUtil.extractMulti</h3>
<p>抽取多个分组然后把它们拼接起来</p> 
<pre><code>String resultExtractMulti = ReUtil.extractMulti("(\\w)aa(\\w)", content, "$1-$2");
Assert.assertEquals("Z-a", resultExtractMulti);
</code></pre>
<h3>ReUtil.delFirst</h3>
<p>删除第一个匹配到的内容</p> 
<pre><code>String resultDelFirst = ReUtil.delFirst("(\\w)aa(\\w)", content);
Assert.assertEquals("ZZbbbccc中文1234", resultDelFirst);
</code></pre>
<h3>ReUtil.findAll</h3>
<p>查找所有匹配文本</p> 
<pre><code>List&lt;String&gt; resultFindAll = ReUtil.findAll("\\w{2}", content, 0, new ArrayList&lt;String&gt;());
ArrayList&lt;String&gt; expected = CollectionUtil.newArrayList("ZZ", "Za", "aa", "bb", "bc", "cc", "12", "34");
Assert.assertEquals(expected, resultFindAll);
</code></pre>
<h3>ReUtil.getFirstNumber</h3>
<p>找到匹配的第一个数字</p> 
<pre><code>Integer resultGetFirstNumber = ReUtil.getFirstNumber(content);
Assert.assertEquals(Integer.valueOf(1234), resultGetFirstNumber);
</code></pre>
<h3>ReUtil.isMatch</h3>
<p>给定字符串是否匹配给定正则</p> 
<pre><code>boolean isMatch = ReUtil.isMatch("\\w+[\u4E00-\u9FFF]+\\d+", content);
Assert.assertTrue(isMatch);
</code></pre>
<h3>ReUtil.replaceAll</h3>
<p>通过正则查找到字符串，然后把匹配到的字符串加入到replacementTemplate中，$1表示分组1的字符串</p> 
<pre><code>//此处把1234替换为 -&gt;1234&lt;-
String replaceAll = ReUtil.replaceAll(content, "(\\d+)", "-&gt;$1&lt;-");
Assert.assertEquals("ZZZaaabbbccc中文-&gt;1234&lt;-", replaceAll);
</code></pre>
<h3>ReUtil.escape</h3>
<p>转义给定字符串，为正则相关的特殊符号转义</p> 
<pre><code>String escape = ReUtil.escape("我有个$符号{}");
Assert.assertEquals("我有个\\$符号\\{\\}", escape);
</code></pre>
			</div>

																						<h4 id="text_215975" class="sin_target">线程工具-ThreadUtil</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p>并发在Java中算是一个比较难理解和容易出问题的部分，而并发的核心在线程。好在从JDK1.5开始Java提供了<code>concurrent</code>包可以很好的帮我们处理大部分并发、异步等问题。</p>
<p>不过，ExecutorService和Executors等众多概念依旧让我们使用这个包变得比较麻烦，如何才能隐藏这些概念？又如何用一个方法解决问题？<code>ThreadUtil</code>便为此而生。</p>
<h2>原理</h2>
<p>Hutool使用<code>GlobalThreadPool</code>持有一个全局的线程池，默认所有异步方法在这个线程池中执行。</p>
<h2>方法</h2>
<h3>ThreadUtil.execute</h3>
<p>直接在公共线程池中执行线程</p>
<h3>ThreadUtil.newExecutor</h3>
<p>获得一个新的线程池</p>
<h3>ThreadUtil.excAsync</h3>
<p>执行异步方法</p>
<h3>ThreadUtil.newCompletionService</h3>
<p>创建CompletionService，调用其submit方法可以异步执行多个任务，最后调用take方法按照完成的顺序获得其结果。若未完成，则会阻塞。</p>
<h3>ThreadUtil.newCountDownLatch</h3>
<p>新建一个CountDownLatch，一个同步辅助类，在完成一组正在其他线程中执行的操作之前，它允许一个或多个线程一直等待。</p>
<h3>ThreadUtil.sleep</h3>
<p>挂起当前线程，是<code>Thread.sleep</code>的封装，通过返回boolean值表示是否被打断，而不是抛出异常。</p> 
<blockquote>
 <p><code>ThreadUtil.safeSleep</code>方法是一个保证挂起足够时间的方法，当给定一个挂起时间，使用此方法可以保证挂起的时间大于或等于给定时间，解决<code>Thread.sleep</code>挂起时间不足问题，此方法在Hutool-cron的定时器中使用保证定时任务执行的准确性。</p> 
</blockquote>
<h3>ThreadUtil.getStackTrace</h3>
<p>此部分包括两个方法：</p> 
<ul> 
 <li><code>getStackTrace</code> 获得堆栈列表</li> 
 <li><code>getStackTraceElement</code> 获得堆栈项</li> 
</ul>
<h3>其它</h3> 
<ul> 
 <li><code>createThreadLocal</code> 创建本地线程对象</li> 
 <li><code>interupt</code> 结束线程，调用此方法后，线程将抛出InterruptedException异常</li> 
 <li><code>waitForDie</code> 等待线程结束. 调用 <code>Thread.join()</code> 并忽略 InterruptedException</li> 
 <li><code>getThreads</code> 获取JVM中与当前线程同组的所有线程</li> 
 <li><code>getMainThread</code> 获取进程的主线程</li> 
</ul>
			</div>

																						<h4 id="text_215976" class="sin_target">URL工具-URLUtil</h4>
<div class="sin_text">
    					<h2>介绍</h2>
<p>URL（Uniform Resource Locator）中文名为统一资源定位符，有时也被俗称为网页地址。表示为互联网上的资源，如网页或者FTP地址。在Java中，也可以使用URL表示Classpath中的资源（Resource）地址。</p>
<h2>方法</h2>
<h3>获取URL对象</h3> 
<ul> 
 <li><code>URLUtil.url</code> 通过一个字符串形式的URL地址创建对象</li> 
 <li><code>URLUtil.getURL</code> 主要获得ClassPath下资源的URL，方便读取Classpath下的配置文件等信息。</li> 
</ul>
<h3>其它</h3> 
<ul> 
 <li><code>URLUtil.formatUrl</code> 格式化URL链接。对于不带http://头的地址做简单补全。</li> 
 <li><code>URLUtil.encode</code> 封装<code>URLEncoder.encode</code>，将需要转换的内容（ASCII码形式之外的内容），用十六进制表示法转换出来，并在之前加上%开头。</li> 
 <li><code>URLUtil.decode</code> 封装<code>URLDecoder.decode</code>，将%开头的16进制表示的内容解码。</li> 
 <li><code>URLUtil.getPath</code> 获得path部分 URI -&gt; <a href="http://www.aaa.bbb/search?scope=ccc&amp;q=ddd" rel="nofollow">http://www.aaa.bbb/search?scope=ccc&amp;q=ddd</a> PATH -&gt; /search</li> 
 <li><code>URLUtil.toURI</code> 转URL或URL字符串为URI。</li> 
</ul>
			</div>

																						<h4 id="text_215977" class="sin_target">XML工具-XmlUtil</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p>在日常编码中，我们接触最多的除了JSON外，就是XML格式了，一般而言，我们首先想到的是引入Dom4j包，却不知JDK已经封装有XML解析和构建工具：w3c dom。但是由于这个API操作比较繁琐，因此Hutool中提供了XmlUtil简化XML的创建、读和写的过程。</p>
<h2>使用</h2>
<h3>读取XML</h3>
<p>读取XML分为两个方法：</p> 
<ul> 
 <li><code>XmlUtil.readXML</code> 读取XML文件</li> 
 <li><code>XmlUtil.parseXml</code> 解析XML字符串为Document对象</li> 
</ul>
<h3>写XML</h3> 
<ul> 
 <li><code>XmlUtil.toStr</code> 将XML文档转换为String</li> 
 <li><code>XmlUtil.toFile</code> 将XML文档写入到文件</li> 
</ul>
<h3>创建XML</h3> 
<ul> 
 <li><code>XmlUtil.createXml</code> 创建XML文档, 创建的XML默认是utf8编码，修改编码的过程是在toStr和toFile方法里，既XML在转为文本的时候才定义编码</li> 
</ul>
<h3>XML操作</h3>
<p>通过以下工具方法，可以完成基本的节点读取操作。</p> 
<ul> 
 <li><code>XmlUtil.cleanInvalid</code> 除XML文本中的无效字符</li> 
 <li><code>XmlUtil.getElements</code> 根据节点名获得子节点列表</li> 
 <li><code>XmlUtil.getElement</code> 根据节点名获得第一个子节点</li> 
 <li><code>XmlUtil.elementText</code> 根据节点名获得第一个子节点</li> 
 <li><code>XmlUtil.transElements</code> 将NodeList转换为Element列表</li> 
</ul>
<h3>XML与对象转换</h3> 
<ul> 
 <li><code>writeObjectAsXml</code> 将可序列化的对象转换为XML写入文件，已经存在的文件将被覆盖。</li> 
 <li><code>readObjectFromXml</code> 从XML中读取对象。</li> 
</ul>
<h2>总结</h2>
<p>XmlUtil只是w3c dom的简单工具化封装，减少操作dom的难度，如果项目对XML依赖较大，依旧推荐Dom4j框架。</p>
			</div>

																						<h4 id="text_215978" class="sin_target">压缩工具-ZipUtil</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p>在Java中，对文件、文件夹打包，压缩是一件比较繁琐的事情，我们常常引入Zip4j进行此类操作。但是很多时候，JDK中的zip包就可满足我们大部分需求。ZipUtil就是针对java.util.zip做工具化封装，使压缩解压操作可以一个方法搞定，并且自动处理文件和目录的问题，不再需要用户判断，压缩后的文件也会自动创建文件，自动创建父目录，大大简化的压缩解压的复杂度。</p>
<h2>方法</h2>
<h3>压缩</h3>
<p><code>ZipUtil.zip</code> 方法提供一系列的重载方法，满足不同需求的压缩需求，这包括：</p> 
<ul> 
 <li>打包到当前目录（可以打包文件，也可以打包文件夹，根据路径自动判断）</li> 
 <li>指定打包后保存的目的地，自动判断目标是文件还是文件夹</li> 
 <li>可选是否包含被打包的目录。比如我们打包一个照片的目录，打开这个压缩包有可能是带目录的，也有可能是打开压缩包直接看到的是文件。zip方法增加一个boolean参数可选这两种模式，以应对众多需求。</li> 
 <li>多文件或目录压缩。可以选择多个文件或目录一起打成zip包。</li> 
</ul>
<h3>解压</h3>
<p><code>ZipUtil.unzip</code> 解压。同样提供几个重载，满足不同需求。</p>
<h3>Gzip压缩和解压</h3>
<p>Gzip是网页传输中广泛使用的压缩方式，Hutool同样提供其工具方法简化其过程。</p>
<p><code>ZipUtil.gzip</code> 压缩，可压缩字符串，也可压缩文件<br><code>ZipUtil.unGzip</code> 解压Gzip文件</p>
			</div>

																						<h3 id="category_52941">语言特性</h3>
																						<h4 id="text_215979" class="sin_target">单例工具-Singleton</h4>
<div class="sin_text">
    					<h2>为什么会有这个类</h2>
<p>平常我们使用单例不外乎两种方式：</p> 
<ol> 
 <li>在对象里加个静态方法getInstance()来获取。此方式可以参考 <a href="http://my.oschina.net/looly/blog/152865" rel="nofollow">【转】线程安全的单例模式</a> 这篇博客，可分为饿汉和饱汉模式。</li> 
 <li>通过Spring这类容器统一管理对象，用的时候去对象池中拿。Spring也可以通过配置决定懒汉或者饿汉模式</li> 
</ol>
<p>说实话我更倾向于第二种，但是Spring更对的的注入，而不是拿，于是我想做Singleton这个类，维护一个单例的池，用这个单例对象的时候直接来拿就可以，这里我用的懒汉模式。我只是想把单例的管理方式换一种思路，我希望管理单例的是一个容器工具，而不是一个大大的框架，这样能大大减少单例使用的复杂性。</p>
<h2>使用</h2> 
<pre><code>package com.xiaoleilu.hutool.demo;

import com.xiaoleilu.hutool.Singleton;

/**
 * 单例样例
 * @author loolly
 *
 */
public class SingletonDemo {
	
	/**
	 * 动物接口
	 * @author loolly
	 *
	 */
	public static interface Animal{
		public void say();
	}
	
	/**
	 * 狗实现
	 * @author loolly
	 *
	 */
	public static class Dog implements Animal{
		@Override
		public void say() {
			System.out.println("汪汪");
		}
	}
	
	/**
	 * 猫实现
	 * @author loolly
	 *
	 */
	public static class Cat implements Animal{
		@Override
		public void say() {
			System.out.println("喵喵");
		}
	}
	
	public static void main(String[] args) {
		Animal dog = Singleton.get(Dog.class);
		Animal cat = Singleton.get(Cat.class);
		
		//单例对象每次取出为同一个对象，除非调用Singleton.destroy()或者remove方法
		System.out.println(dog == Singleton.get(Dog.class));		//True
		System.out.println(cat == Singleton.get(Cat.class));			//True
		
		dog.say();		//汪汪
		cat.say();		//喵喵
	}
}
</code></pre>
<h2>总结</h2>
<p>大家如果有兴趣可以看下这个类，实现非常简单，一个HashMap用于做为单例对象池，通过newInstance()实例化对象（不支持带参数的构造方法），无论取还是创建对象都是线程安全的（在单例对象数量非常庞大且单例对象实例化非常耗时时可能会出现瓶颈），考虑到在get的时候使双重检查锁，但是并不是线程安全的，故直接加了<code>synchronized</code>做为修饰符，欢迎在此给出建议。</p>
			</div>

																						<h4 id="text_215980" class="sin_target">base32编码解码-Base32</h4>
<div class="sin_text">
    					<h2>介绍</h2>
<p>Base32就是用32（2的5次方）个特定ASCII码来表示256个ASCII码。所以，5个ASCII字符经过base32编码后会变为8个字符（公约数为40），长度增加3/5.不足8n用“=”补足。</p>
<h2>使用</h2> 
<pre><code>String a = "伦家是一个非常长的字符串";

String encode = Base32.encode(a);
Assert.assertEquals("4S6KNZNOW3TJRL7EXCAOJOFK5GOZ5ZNYXDUZLP7HTKCOLLMX46WKNZFYWI", encode);
		
String decodeStr = Base32.decodeStr(encode);
Assert.assertEquals(a, decodeStr);
</code></pre>
			</div>

																						<h4 id="text_215981" class="sin_target">base64编码和解码-Base64</h4>
<div class="sin_text">
    					<h2>介绍</h2>
<p>base64编码是用64（2的6次方）个ASCII字符来表示256（2的8次方）个ASCII字符，也就是三位二进制数组经过编码后变为四位的ASCII字符显示，长度比原来增加1/3。</p>
<h2>使用</h2> 
<pre><code>String a = "伦家是一个非常长的字符串";
String encode = Base64.encode(a);
Assert.assertEquals("5Lym5a625piv5LiA5Liq6Z2e5bi46ZW/55qE5a2X56ym5Liy", encode);

String decodeStr = Base64.decodeStr(encode);
Assert.assertEquals(a, decodeStr);
</code></pre>
			</div>

																						<h4 id="text_215982" class="sin_target">有界优先队列-BoundedPriorityQueue</h4>
<div class="sin_text">
    					<h2>简介</h2>
<p>举个例子。我有一个用户表，这个表根据用户名被Hash到不同的数据库实例上，我要找出这些用户中最热门的5个，怎么做？我是这么做的：</p> 
<ol> 
 <li>在每个数据库实例上找出最热门的5个</li> 
 <li>将每个数据库实例上的这5条数据按照热门程度排序，最后取出前5条</li> 
</ol>
<p>这个过程看似简单，但是你应用服务器上的代码要写不少。首先需要Query N个列表，加入到一个新列表中，排序，再取前5。这个过程不但代码繁琐，而且牵涉到多个列表，非常浪费空间。</p>
<p>于是，<code>BoundedPriorityQueue</code>应运而生。</p>
<p>先看Demo：</p> 
<pre><code>/**
 * 有界优先队列Demo
 * @author Looly
 *
 */
public class BoundedPriorityQueueDemo {
	
	public static void main(String[] args) {
		//初始化队列，设置队列的容量为5（只能容纳5个元素），元素类型为integer使用默认比较器，在队列内部将按照从小到大排序
		BoundedPriorityQueue&lt;Integer&gt; queue = new BoundedPriorityQueue&lt;Integer&gt;(5);
                
                //初始化队列，使用自定义的比较器
		queue = new BoundedPriorityQueue&lt;&gt;(5, new Comparator&lt;Integer&gt;(){

			@Override
			public int compare(Integer o1, Integer o2) {
				return o1.compareTo(o2);
			}
		});
		
		//定义了6个元素，当元素加入到队列中，会按照从小到大排序，当加入第6个元素的时候，队列末尾（最大的元素）将会被抛弃
		int[] array = new int[]{5,7,9,2,3,8};
		for (int i : array) {
			queue.offer(i);
		}
		
                //队列可以转换为List哦~~
		ArrayList&lt;Integer&gt; list = queue.toList();

		System.out.println(queue);
	}
}
</code></pre>
<p>原理非常简单。设定好队列的容量，然后把所有的数据add或者offer进去（两个方法相同），就会得到前5条数据了。</p>
			</div>

																						<h4 id="text_215983" class="sin_target">字段验证器-Validator</h4>
<div class="sin_text">
    					<h2>作用</h2>
<p>验证给定字符串是否满足指定条件，一般用在表单字段验证里。</p>
<p>此类中全部为静态方法。</p>
<h2>使用</h2>
<h3>判断验证</h3>
<p>直接调用<code>Validator.isXXX(String value)</code>既可验证字段，返回是否通过验证。</p>
<p>例如：</p> 
<pre><code>boolean isEmail = Validator.isEmail("loolly@gmail.com")
</code></pre>
<p>表示验证给定字符串是否复合电子邮件格式。</p>
<p>其他验证信息请参阅<code>Validator</code>类</p>
<p>如果Validator里的方法无法满足自己的需求，那还可以调用</p> 
<pre><code>Validator.isByRegex("需要验证字段的正则表达式", "被验证内容")
</code></pre>
<p>来通过正则表达式灵活的验证内容。</p>
<h3>异常验证</h3>
<p>除了手动判断，我们有时需要在判断未满足条件时抛出一个异常，Validator同样提供异常验证机制：</p> 
<pre><code>Validator.validateChinese("我是一段zhongwen", "内容中包含非中文");
</code></pre>
<p>因为内容中包含非中文字符，因此会抛出ValidateException。</p>
			</div>

																						<h4 id="text_215984" class="sin_target">控制台打印封装-Console</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p>编码中我们常常需要调试输出一些信息，除了打印日志，最长用的要数<code>System.out</code>和<code>System.err</code></p>
<p>比如我们打印一个Hello World，可以这样写：</p> 
<pre><code>System.out.println("Hello World");
</code></pre>
<p>但是面对纷杂的打印需求，<code>System.out.println</code>无法满足，比如：<br>1. 不支持参数，对象打印需要拼接字符串<br>2. 不能直接打印数组，需要手动调用<code>Arrays.toString</code></p>
<p>考虑到以上问题，我封装了<code>Console</code>对象。</p> 
<blockquote>
 <p>Console对象的使用更加类似于Javascript的<code>console.log()</code>方法，这也是借鉴了JS的一个语法糖。</p> 
</blockquote>
<h2>使用</h2> 
<ol> 
 <li><code>Console.log</code> 这个方法基本等同于<code>System.out.println</code>,但是支持类似于Slf4j的字符串模板语法，同时也会自动将对象（包括数组）转为字符串形式。</li> 
</ol> 
<pre><code>String[] a = {"abc", "bcd", "def"};
Console.log(a);//控制台输出：[abc, bcd, def]
</code></pre> 
<pre><code>Console.log("This is Console log for {}.", "test");
//控制台输出：This is Console log for test.
</code></pre> 
<ol> 
 <li><code>Console.error</code> 这个方法基本等同于<code>System.err.println</code>，,但是支持类似于Slf4j的字符串模板语法，同时也会自动将对象（包括数组）转为字符串形式。</li> 
</ol>
			</div>

																						<h4 id="text_215985" class="sin_target">二进码十进数-BCD</h4>
<div class="sin_text">
    					<h2>介绍</h2>
<p>BCD码（Binary-Coded Decimal‎）亦称二进码十进数或二-十进制代码，BCD码这种编码形式利用了四个位元来储存一个十进制的数码，使二进制和十进制之间的转换得以快捷的进行。</p>
<p>这种编码技巧最常用于会计系统的设计里，因为会计制度经常需要对很长的数字串作准确的计算。相对于一般的浮点式记数法，采用BCD码，既可保存数值的精确度，又可免却使电脑作浮点运算时所耗费的时间。此外，对于其他需要高精确度的计算，BCD编码亦很常用。</p>
<p>BCD码是四位二进制码, 也就是将十进制的数字转化为二进制, 但是和普通的转化有一点不同, 每一个十进制的数字0-9都对应着一个四位的二进制码,对应关系如下: 十进制0 对应 二进制0000 ;十进制1 对应二进制0001 ……. 9 1001 接下来的10就有两个上述的码来表示 10 表示为00010000 也就是BCD码是遇见1001就产生进位,不象普通的二进制码,到1111才产生进位10000</p>
<h2>方法</h2> 
<pre><code>String strForTest = "123456ABCDEF";
		
//转BCD
byte[] bcd = BCD.strToBcd(strForTest);
//解码BCD
String str = BCD.bcdToStr(bcd);
Assert.assertEquals(strForTest, str);
</code></pre>
			</div>

																						<h4 id="text_215986" class="sin_target">HashMap扩展-Dict</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p>如果你了解Python，你一定知道Python有dict这一数据结构，也是一种KV（Key-Value）结构的数据结构，类似于Java中的Map，但是提供了更加灵活多样的使用。Hutool中的Dict对象旨在实现更加灵活的KV结构，针对强类型，提供丰富的getXXX操作，将HashMap扩展为无类型区别的数据结构。</p>
<h2>介绍</h2>
<p>Dict继承HashMap，其key为String类型，value为Object类型，通过实现<code>BasicTypeGetter</code>接口提供针对不同类型的get方法，同时提供针对Bean的转换方法，大大提高Map的灵活性。</p> 
<blockquote>
 <p>Hutool-db中Entity是Dict子类，做为数据的媒介。</p> 
</blockquote>
<h2>使用</h2>
<h3>创建</h3> 
<pre><code>Dict dict = Dict.create()
	.set("key1", 1)//int
	.set("key2", 1000L)//long
	.set("key3", DateTime.now());//Date
</code></pre>
<p>通过链式构造，创建Dict对象，同时可以按照Map的方式使用。</p>
<h3>获取指定类型的值</h3> 
<pre><code>Long v2 = dict.getLong("key2");//1000
</code></pre>
			</div>

																						<h4 id="text_215987" class="sin_target">字符串格式化-StrFormatter</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p>我一直对Slf4j的字符串格式化情有独钟，通过<code>{}</code>这种简单的占位符完成字符串的格式化。于是参考Slf4j的源码，便有了<code>StrFormatter</code>。</p> 
<blockquote>
 <p>StrFormatter.format的快捷使用方式为<code>StrUtil.format</code>，推荐使用后者。</p> 
</blockquote>
<h2>使用</h2> 
<pre><code>//通常使用
String result1 = StrFormatter.format("this is {} for {}", "a", "b");
Assert.assertEquals("this is a for b", result1);

//转义{}
String result2 = StrFormatter.format("this is \\{} for {}", "a", "b");
Assert.assertEquals("this is {} for a", result2);

//转义\
String result3 = StrFormatter.format("this is \\\\{} for {}", "a", "b");
Assert.assertEquals("this is \\a for b", result3);
</code></pre>
			</div>

																							<h4 id="text_215988" class="sin_target">断言-Assert</h4>
<div class="sin_text">
    					<p>url:repairOrderSimple/queryRepairOrderPageListApp.action<br>入参: 参数名称:json;<br>json数据格式:<br>{“orderStatus”:[“sb”,“fq”,“ysth”],“equipmentId”:“需要查询的设备号”,“deptId”:“01”,“nextFlag”:“0”,“owner”:“当前登录人的用户id”,“sourceType”:“yx|dj|D”}<br>待处理:{“orderStatus”:[“sb”,“fq”,“ysth”]}<br>待验收:{“orderStatus”:[“ws”]}<br>已验收:{“orderStatus”:[“ys”]}<br>“nextFlag”:“1”（查询下周计划需要传“1”）<br>owner:当前登录人的用户id sourceType:1当是运行tab页时传入yx;2当是点检tab页时传入dj;3当是PMtab页时传入D;<br>出参:<br>{<br> “resultCode”: “1”,<br> “resultMessage”: “接口调用成功”,<br> “result”: [<br> {<br> “requestDept”: “工程部”,<br> “exceptionId”: “20170819-008”,<br> “submitUserName”: “吴勇”,<br> “submitContent”: “污染发生发生的 大”,<br> “repairOrderId”: “QD170819-004650”,<br> “exceptionSubmitId”: “20170819-008”,<br> “orderDate”: “2017-08-19 16:59:01”,<br> “submitTime”: “2017-08-19 16:59:01”,<br> “DEPTNAME”: “工程部”,<br> “RESOURCE_TYPE”: “yx”,<br> “orderStatus”: “sb”,<br> “planSource”: “yx”,<br> “equipmentName”: “二氧化碳设备–冷凝装置–冷凝泵”,<br> “exceptionRemarks”: “污染发生发生的 大”,<br> “ROW_ID”: 1,<br> “equipmentId”: “103030205”,<br> “submitUserId”: “472”,<br> “usableStatus”: “1”,<br> “deptId”: “103”,<br> “requestUserName”: “吴勇”,<br> “requestUserId”: “472”<br> },<br> ……<br> ],<br> “sum(每种情况的数据总数)”: 14<br>}</p>
								<h2>由来</h2>
<p>Java中有<code>assert</code>关键字，但是存在许多问题：<br>1. assert关键字需要在运行时候显式开启才能生效，否则你的断言就没有任何意义。<br>2. 用assert代替if是陷阱之二。assert的判断和if语句差不多，但两者的作用有着本质的区别：assert关键字本意上是为测试调试程序时使用的，但如果不小心用assert来控制了程序的业务流程，那在测试调试结束后去掉assert关键字就意味着修改了程序的正常的逻辑。<br>3. assert断言失败将面临程序的退出。</p>
<p>因此，并不建议使用此关键字。相应的，在Hutool中封装了更加友好的Assert类，用于断言判定。</p>
<h2>介绍</h2>
<p>Assert类更像是Junit中的Assert类，也很像Guava中的Preconditions，主要作用是在方法或者任何地方对参数的有效性做校验。当不满足断言条件时，会抛出IllegalArgumentException或IllegalStateException异常。</p>
<h2>使用</h2> 
<pre><code>String a = null;
com.xiaoleilu.hutool.lang.Assert.isNull(a);
</code></pre>
<h2>更方法</h2> 
<ul> 
 <li>isTrue 是否True</li> 
 <li>isNull 是否是null值，不为null抛出异常</li> 
 <li>notNull 是否非null值</li> 
 <li>notEmpty 是否非空</li> 
 <li>notBlank 是否非空白符</li> 
 <li>notContain 是否为子串</li> 
 <li>notEmpty 是否非空</li> 
 <li>noNullElements 数组中是否包含null元素</li> 
 <li>isInstanceOf 是否类实例</li> 
 <li>isAssignable 是子类和父类关系</li> 
 <li>state 会抛出IllegalStateException异常</li> 
</ul>
			</div>

																						<h3 id="category_52942">JavaBean</h3>
																						<h4 id="text_215989" class="sin_target">概述</h4>
<div class="sin_text">
    					<h2>概述</h2>
<p>针对JavaBean已经有BeanUtil的工具封装，我认为这还不够。最近看了Apache Commons BeanUtils的DynaBean源码和Nuts中Mirror类的文档（请看这里 -&gt; <a href="http://nutzam.com/core/lang/mirror.html" rel="nofollow">增强反射</a>），启发颇多，于是我决定在Hutool中加入DynaBean。</p>
<p>Dyna既Dynamic，顾名思义，通过Java反射机制操作JavaBean，以达到动态语言的某些特性。</p>
<p>bean包的另一个改进是针对PropertyDescriptor提供缓存。BeanInfoCache类缓存了通过内省获取到的PropertyDescriptor，以提高反射性能。</p>
			</div>

																						<h4 id="text_215990" class="sin_target">DynaBean</h4>
<div class="sin_text">
    					<h2>介绍</h2>
<p>DynaBean是使用反射机制动态操作JavaBean的一个封装类，通过这个类，可以通过字符串传入name方式动态调用get和set方法，也可以动态创建JavaBean对象，亦或者执行JavaBean中的方法。</p>
<h2>使用</h2>
<p>我们先定义一个JavaBean：</p> 
<pre><code>public static class User{
	private String name;
	private int age;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
	public String testMethod(){
		return "test for " + this.name;
	}
	
	@Override
	public String toString() {
		return "User [name=" + name + ", age=" + age + "]";
	}
}
</code></pre>
<h3>创建</h3> 
<pre><code>DynaBean bean = DynaBean.create(user);
//我们也可以通过反射构造对象
DynaBean bean2 = DynaBean.create(User.class);
</code></pre>
<h3>操作</h3>
<p>我们通过DynaBean来包装并操作这个Bean</p> 
<pre><code>User user = new User();
DynaBean bean = DynaBean.create(user);
bean.set("name", "李华");
bean.set("age", 12);

String name = bean.get("name");//输出“李华”
</code></pre>
<p>这样我们就可以像操作Map一样动态操作JavaBean</p>
<h3>invoke</h3>
<p>除了标准的get和set方法，也可以调用invoke方法执行对象中的任意方法：</p> 
<pre><code>//执行指定方法
Object invoke = bean2.invoke("testMethod");
Assert.assertEquals("test for 李华", invoke);
</code></pre> 
<blockquote>
 <p>说明:<br>DynaBean默认实现了hashCode、equals和toString三个方法，这三个方法也是默认调用原对象的相应方法操作。</p> 
</blockquote>
			</div>

																						<h4 id="text_215991" class="sin_target">表达式解析-BeanResolver</h4>
<div class="sin_text">
    					<h2>由来</h2>
<p>很多JavaBean嵌套有很多层对象，这其中还夹杂着Map、Collection等对象，因此获取太深的嵌套对象会让代码变得冗长不堪。因此我们可以考虑使用一种表达式还获取指定深度的对象，于是BeanResolver应运而生。</p>
<h2>原理</h2>
<p>通过传入一个表达式，按照表达式的规则获取bean下指定的对象。</p>
<p>表达式分为两种：<br>- <code>.</code>表达式，可以获取Bean对象中的属性（字段）值或者Map中key对应的值<br>- <code>[]</code>表达式，可以获取集合等对象中对应index的值</p>
<p>栗子：<br>1. <code>persion</code> 获取Bean对象下person字段的值，或者Bean本身如果是Person对象，返回本身。<br>2. <code>persion.name</code> 获取Bean中person字段下name字段的值，或者Bean本身如果是Person对象，返回其name字段的值。<br>3. <code>persons[3]</code> 获取persons字段下第三个元素的值（假设person是数组或Collection对象）<br>4. <code>person.friends[5].name</code> 获取person字段下friends列表（或数组）的第5个元素对象的name属性</p>
<h2>使用</h2>
<p>由于嵌套Bean定义过于复杂，在此我们省略，有兴趣的可以看下这里：com.xiaoleilu.hutool.core.lang.test.bean（src/test/java下）下定义了测试用例用的bean。</p>
<p>首先我们创建这个复杂的Bean（实际当中这个复杂的Bean可能是从数据库中获取，或者从JSON转入）</p>
<p>这个复杂Bean的关系是这样的：</p>
<p>定义一个Map包含用户信息（UserInfoDict）和一个标志位（flag），用户信息包括一些基本信息和一个考试信息列表（ExamInfoDict）。</p> 
<pre><code>//------------------------------------------------- 考试信息列表
ExamInfoDict examInfoDict = new ExamInfoDict();
examInfoDict.setId(1);
examInfoDict.setExamType(0);
examInfoDict.setAnswerIs(1);

ExamInfoDict examInfoDict1 = new ExamInfoDict();
examInfoDict1.setId(2);
examInfoDict1.setExamType(0);
examInfoDict1.setAnswerIs(0);

ExamInfoDict examInfoDict2 = new ExamInfoDict();
examInfoDict2.setId(3);
examInfoDict2.setExamType(1);
examInfoDict2.setAnswerIs(0);

List&lt;ExamInfoDict&gt; examInfoDicts = new ArrayList&lt;ExamInfoDict&gt;();
examInfoDicts.add(examInfoDict);
examInfoDicts.add(examInfoDict1);
examInfoDicts.add(examInfoDict2);

//------------------------------------------------- 用户信息
UserInfoDict userInfoDict = new UserInfoDict();
userInfoDict.setId(1);
userInfoDict.setPhotoPath("yx.mm.com");
userInfoDict.setRealName("张三");
userInfoDict.setExamInfoDict(examInfoDicts);

Map&lt;String, Object&gt; tempMap = new HashMap&lt;String, Object&gt;();
tempMap.put("userInfo", userInfoDict);
tempMap.put("flag", 1);
</code></pre>
<p>下面，我们使用<code>BeanResolver</code>获取这个Map下此用户第一门考试的ID：</p> 
<pre><code>BeanResolver resolver = new BeanResolver(tempMap, "userInfo.examInfoDict[0].id");
Object result = resolver.resolve();//ID为1
</code></pre>
<p>只需两句（甚至一句）即可完成复杂Bean中各层次对象的获取。</p> 
<blockquote>
 <p>说明：<br>为了简化<code>BeanResolver</code>的使用，Hutool在BeanUtil中也加入了快捷入口方法：<code>BeanUtil.getProperty</code>，这个方法的命名更容易理解（毕竟BeanResolver不但可以解析Bean，而且可以解析Map和集合）。</p> 
</blockquote>
			</div>

																																				<h2 id="category_52944">配置文件(Hutool-setting)</h2>
																								<h3 id="text_215992" class="sin_target">配置文件模块概述</h3>
<div class="sin_text">
    					<h2>由来</h2>
<h3>Setting</h3>
<p>众所周知，Java中广泛应用的配置文件Properties存在一个特别大的诟病：不支持中文。每次使用时，如果想存放中文字符，必须借助IDE相关插件才能转为Unicode符号，而这种反人类的符号在命令行下根本没法看（想想部署在服务器上后修改配置文件是一件多么痛苦的事情）</p>
<p>于是，在很多框架中开始渐渐抛弃Properties文件而转向XML配置文件（例如Hibernate和Spring早期版本）。但是XML罗嗦的配置方式实在无法忍受。于是，Setting诞生。</p>
<h3>Props</h3>
<p>Properties的第二个问题是读取非常不方便，需要我们自己写长长的代码进行load操作：</p> 
<pre><code>properties = new Properties();
try {
	Class clazz = Demo1.class;
	InputStream inputestream = clazz.getResourceAsStream("db.properties");
	properties.load( inputestream);
}catch (IOException e) {
	//ignore
}
</code></pre>
<p>而Props则大大简化为：<br> </p>
<pre><code><br>Props props = new Props("db.properties");
</code></pre>
<p></p>
<p>考虑到Properties使用依旧广泛，因此封装了Props类以应对兼容性。</p>
			</div>

																						<h3 id="text_215993" class="sin_target">设置文件-Setting</h3>
<div class="sin_text">
    					<h2>简介</h2>
<p>Setting除了兼容Properties文件格式外，还提供了一些特有功能，这些功能包括：<br>- 各种编码方式支持<br>- 变量支持<br>- 分组支持</p>
<p>首先说编码支持，在Properties中，只支<code>ISO8859-1</code>导致在Properties文件中注释和value没法使用中文，（用日本的那个插件在Eclipse里可以读写，放到服务器上读就费劲了），因此Setting中引入自定义编码，可以很好的支持各种编码的配置文件。</p>
<p>再就是变量支持，在Setting中，支持${key}变量，可以将之前定义的键对应的值做为本条值得一部分，这个特性可以减少大量的配置文件冗余。</p>
<p>最后是分组支持。分组的概念我第一次在Linux的rsync的/etc/rsyncd.conf配置文件中有所了解，发现特别实用，具体大家可以自行百度之。当然，在Windows的ini文件中也有分组的概念，Setting将这一概念引入，从而大大增加配置文件的可读性。</p>
<h2>配置文件格式example.setting</h2> 
<pre><code># -------------------------------------------------------------
# ----- Setting File with UTF8-----
# ----- 数据库配置文件 -----
# -------------------------------------------------------------

#中括表示一个分组，其下面的所有属性归属于这个分组，在此分组名为demo，也可以没有分组
[demo]
#自定义数据源设置文件，这个文件会针对当前分组生效，用于给当前分组配置单独的数据库连接池参数，没有则使用全局的配置
ds.setting.path = config/other.setting
#数据库驱动名，如果不指定，则会根据url自动判定
driver = com.mysql.jdbc.Driver
#JDBC url，必须
url = jdbc:mysql://fedora.vmware:3306/extractor
#用户名，必须
user = root${demo.driver}
#密码，必须，如果密码为空，请填写 pass = 
pass = 123456
</code></pre>
<p>配置文件可以放在任意位置，具体Setting类如何寻在在构造方法中提供了多种读取方式，具体稍后介绍。现在说下配置文件的具体格式<br>Setting配置文件类似于Properties文件，规则如下：</p> 
<ol> 
 <li>注释用<code>#</code>开头表示，只支持单行注释，空行和无法正常被识别的键值对也会被忽略，可作为注释，但是建议显式指定注释。</li> 
 <li>键值对使用key = value 表示，key和value在读取时会trim掉空格，所以不用担心空格。</li> 
 <li>分组为中括号括起来的内容（例如配置文件中的<code>[demo]</code>），中括号以下的行都为此分组的内容，无分组相当于空字符分组，即<code>[]</code>。若某个<code>key</code>是<code>name</code>，分组是<code>group</code>，加上分组后的key相当于group.name。</li> 
 <li>支持变量，默认变量命名为 ${变量名}，变量只能识别读入行的变量，例如第6行的变量在第三行无法读取，例如配置文件中的${driver}会被替换为com.mysql.jdbc.Driver，为了性能，Setting创建的时候构造方法会指定是否开启变量替换，默认不开启。</li> 
</ol>
<h2>代码</h2>
<p>代码具体请见<code>com.xiaoleilu.hutool.demo.SettingDemo</code></p> 
<pre><code>package com.xiaoleilu.hutool.demo;

import java.io.IOException;

import com.xiaoleilu.hutool.CharsetUtil;
import com.xiaoleilu.hutool.FileUtil;
import com.xiaoleilu.hutool.Setting;

/**
 * Setting演示样例类
 * @author Looly
 *
 */
public class SettingDemo {
	public static void main(String[] args) throws IOException {
		//--------------------------------------------- 初始化
		//读取classpath下的XXX.setting，不使用变量
		Setting setting = new Setting("XXX.setting");
		
		//读取classpath下的config目录下的XXX.setting，不使用变量
		setting = new Setting("config/XXX.setting");
		
		//读取绝对路径文件/home/looly/XXX.setting（没有就创建，关于touch请查阅FileUtil）
		//第二个参数为自定义的编码，请保持与Setting文件的编码一致
		//第三个参数为是否使用变量，如果为true，则配置文件中的每个key都可以被之后的条目中的value引用形式为 ${key}
		setting = new Setting(FileUtil.touch("/home/looly/XXX.setting"), CharsetUtil.UTF_8, true);
		
		//读取与SettingDemo.class文件同包下的XXX.setting
		setting = new Setting("XXX.setting", SettingDemo.class, CharsetUtil.UTF_8, true);
		
		//--------------------------------------------- 使用
		//获取key为name的值
		setting.getString("name");
		//获取分组为group下key为name的值
		setting.getString("name", "group1");
		//当获取的值为空（null或者空白字符时，包括多个空格），返回默认值
		setting.getStringWithDefault("name", "默认值");
		//完整的带有key、分组和默认值的获得值得方法
		setting.getStringWithDefault("name", "group1", "默认值");
		
		//如果想获得其它类型的值，可以调用相应的getXXX方法，参数相似
		
		//有时候需要在key对应value不存在的时候（没有这项设置的时候）告知用户，故有此方法打印一个debug日志
		setting.getWithLog("name");
		setting.getWithLog("name", "group1");
		
		//重新读取配置文件，可以启用一个定时器调用此方法来定时更新配置
		setting.reload();
		
		//当通过代码加入新的键值对的时候，调用store会保存到文件，但是会覆盖原来的文件，并丢失注释
		setting.setSetting("name1", "value");
		setting.store("/home/looly/XXX.setting");

		//获得所有分组名
		setting.getGroups();
		
		//将key-value映射为对象，原理是原理是调用对象对应的setXX方法
		//setting.toObject();
		
		//设定变量名的正则表达式。
		//Setting的变量替换是通过正则查找替换的，如果Setting中的变量名和其他冲突，可以改变变量的定义方式
		//整个正则匹配变量名，分组1匹配key的名字
		setting.setVarRegex("\\$\\{(.*?)\\}");
	}
}
</code></pre>
			</div>

																						<h3 id="text_215994" class="sin_target">Properties扩展-Props</h3>
<div class="sin_text">
    					<h2>介绍</h2>
<p>对于Properties的广泛使用使我也无能为力，有时候遇到Properties文件又想方便的读写也不容易，于是对Properties做了简单的封装，提供了方便的构造方法（与Setting一致），并提供了与Setting一致的getXXX方法来扩展Properties类，<code>Props</code>类继承自Properties，所以可以兼容Properties类。</p>
<h2>使用</h2>
<p>Props的使用方法和Properties以及Setting一致（同时支持）：<br> </p>
<pre><code><br>Props props = new Props("test.properties");
String user = props.getProperty("user");
String driver = props.getStr("driver");
</code></pre>
<p></p>
			</div>

																														<h2 id="category_52945">日志(Hutool-log)</h2>
																								<h3 id="text_215995" class="sin_target">Log模块概述</h3>
<div class="sin_text">
    					<h2>由来</h2>
<p>准确的说，Hutool-log只是一个日志的通用门面，功能类似于Slf4j。既然像Slf4j这种门面框架已经非常完善，为何还要自己做一个门面呢？下面我列举实践中遇到的一些问题：</p>
<h2>已有门面存在问题</h2> 
<ol> 
 <li>log对象创建比较复杂</li> 
</ol>
<p>很多时候我们为了在类中加日志不得不写一行，而且还要去手动改XXX这个类名</p> 
<pre><code>private static final Logger log = LoggerFactory.getLogger(XXX.class);
</code></pre> 
<ol> 
 <li>对于附带Exception参数的方法，并不支持变量。</li> 
</ol>
<p>Slf4j中我最喜欢的形式，这样既省去了麻烦的isInfoEnabled()的判断，还避免了拼接字符串： </p> 
<pre><code>log.info("我在XXX 改了 {} 变量", "name");
</code></pre>
<p>但是这种情况下就无法使用变量模式：</p> 
<pre><code>log.error("错误消息", e);
</code></pre>
<h2>特点</h2> 
<ol> 
 <li>Logfactory.get方法不再需要（或者不是必须）传入当前类名，会自定解析当前类名</li> 
 <li>log.xxx方法在传入Exception时同时支持模板语法。</li> 
 <li>不需要桥接包而自动适配引入的日志框架，在无日志框架下依旧可以完美适配JDK Logging。</li> 
 <li>引入多个日志框架情况下，可以自定义日志框架输出。</li> 
</ol>
<h2>原理</h2>
<p>Hutool-log采用动态自动适配模式，它会自动检测引入的日志框架包从而将日志输出到此框架。<br>比如我们在项目中引入Log4j的包，Hutool-log会自动检测到此包的存在，并将日志输出到log4j。如果没有引入任何日志框架，会将日志输出到JDK Logging中。</p>
<p>因此，Hutool-log并没有统一的配置文件，如果你引入任何一种日志框架，使用此框架的配置文件即可。</p>
<p>Hutool-log对于日志框架的监测顺序是：<br>Slf4j(Logback) &gt; Log4j &gt; Log4j2 &gt; Apache Commons Logging &gt; JDK Logging &gt; Console</p>
<p>当然，如果只是引入Slf4j-API，而没有引入任何实现，Slf4j将被跳过。</p> 
<blockquote>
 <p>关于日志框架监测的核心代码请参阅：<code>LogFactory.detectLogFactory</code></p> 
</blockquote>
<h2>使用</h2>
<h3>常规使用</h3>
<p>Hutool-log的使用与一般日志框架并无区别，调用<code>LogFactory.get()</code>即可简单获取Log实现对象。</p> 
<pre><code>Log log = LogFactory.get();

log.debug("This is {} log", Level.DEBUG);
log.info("This is {} log", Level.INFO);
log.warn("This is {} log", Level.WARN);

Exception e = new Exception("test Exception");
log.error(e, "This is {} log", Level.ERROR);
</code></pre> 
<blockquote>
 <p>通常我们需要在类中定义日志为<code>private static final Log log = LogFactory.get();</code>以获得更好的性能。</p> 
</blockquote>
<h3>自定义日志实现</h3>
<p>有的时候，我们需要自定义日志框架输出，这是我们就需要调用<code>LogFactory.setCurrentLogFactory</code>方法来定义全局的日志实现。</p> 
<pre><code>// 自动选择日志实现
Log log = LogFactory.get();
log.debug("This is {} log", "default");
Console.log("----------------------------------------------------------------------");

//自定义日志实现为Apache Commons Logging
LogFactory.setCurrentLogFactory(new ApacheCommonsLogFactory());
log.debug("This is {} log", "custom apache commons logging");
Console.log("----------------------------------------------------------------------");

//自定义日志实现为JDK Logging
LogFactory.setCurrentLogFactory(new JdkLogFactory());
log.info("This is {} log", "custom jdk logging");
Console.log("----------------------------------------------------------------------");

//自定义日志实现为Console Logging
LogFactory.setCurrentLogFactory(new ConsoleLogFactory());
log.info("This is {} log", "custom Console");
Console.log("----------------------------------------------------------------------");
</code></pre> 
<blockquote>
 <p>默认的，Console log并不会使用到，因为JDK Logging总是存在。因此，想使用命令行输出，需要自定义日志实现为<code>ConsoleLogFactory</code></p> 
</blockquote>
			</div>

																						<h3 id="text_215996" class="sin_target">日志工厂-LogFactory</h3>
<div class="sin_text">
    					<h2>介绍</h2>
<p>Hutool-log做为一个日志门面，为了兼容各大日志框架，一个用于自动创建日志对象的日志工厂类必不可少。</p>
<p><code>LogFactory</code>类用于灵活的创建日志对象，通过static方法创建我们需要的日志，主要功能如下：</p> 
<ul> 
 <li><p><code>LogFactory.get</code> 自动识别引入的日志框架，从而创建对应日志框架的门面Log对象（此方法创建一次后，下次再次get会根据传入类名缓存Log对象，对于每个类，Log对象都是单例的），同时自动识别当前类，将当前类做为类名传入日志框架。</p></li> 
 <li><p><code>LogFactory.createLog</code> 与get方法作用类似。但是此方法调用后会每次创建一个新的Log对象。</p></li> 
 <li><p><code>LogFactory.setCurrentLogFactory</code> 自定义当前日志门面的日志实现类。当引入多个日志框架时，我们希望自定义所用的日志框架，调用此方法即可。需要注意的是，此方法为全局方法，在获取Log对象前只调用一次即可。</p></li> 
</ul>
<h2>使用</h2>
<h3>获取当前类对应的Log对象：</h3> 
<pre><code>//推荐创建不可变静态类成员变量
private static final Log log = LogFactory.get();
</code></pre>
<p>如果你想获得自定义name的Log对象（像普通Log日志实现一样），那么可以使用如下方式获取Log：</p> 
<pre><code>private static final Log log = LogFactory.get("我是一个自定义日志名");
</code></pre>
<h3>自定义日志实现</h3> 
<pre><code>//自定义日志实现为Apache Commons Logging
LogFactory.setCurrentLogFactory(new ApacheCommonsLogFactory());

//自定义日志实现为JDK Logging
LogFactory.setCurrentLogFactory(new JdkLogFactory());

//自定义日志实现为Console Logging
LogFactory.setCurrentLogFactory(new ConsoleLogFactory());
</code></pre>
<h3>自定义日志工厂（自定义日志门面实现）</h3>
<p>LogFactory是一个抽象类，我们可以继承此类，实现<code>createLog</code>方法即可（同时我们可能需要实现Log接口来达到自定义门面的目的），这样我们就可以自定义一个日志门面。最后通过<code>LogFactory.setCurrentLogFactory</code>方法装入这个自定义LogFactory即可实现自定义日志门面。</p> 
<blockquote>
 <p>PS<br>自定义日志门面的实现可以参考<code>com.xiaoleilu.hutool.log.dialect</code>包中的实现内容自定义扩展。<br>本质上，实现Log接口，做一个日志实现的Wrapper，然后在相应的工厂类中创建此Log实例即可。同时，LogFactory中还可以初始化一些启动配置参数。</p> 
</blockquote>
			</div>

																						<h3 id="text_215997" class="sin_target">静态调用日志-StaticLog</h3>
<div class="sin_text">
    					<h2>由来</h2>
<p>很多时候，我们只是想简简单的使用日志，最好一个方法搞定，我也不想创建Log对象，那么<code>StaticLog</code>或许是你需要的。</p>
<h2>使用</h2> 
<pre><code>StaticLog.info("This is static {} log.", "INFO");
</code></pre>
<p>同样StaticLog提供了trace、debug、info、warn、error方法，提供变量占位符支持，使项目中日志的使用简单到没朋友。</p>
<p>StaticLog类中同样提供log方法，可能在极致简洁的状况下，提供非常棒的灵活性（打印日志等级由level参数决定）</p>
<h2>与LogFactory同名方法</h2>
<p>假如你只知道StaticLog，不知道LogFactory怎么办？Hutool非常贴心的提供了<code>get</code>方法，此方法与Logfactory中的<code>get</code>方法一样，同样可以获得Log对象。</p>
			</div>

																						<h3 id="text_215998" class="sin_target">疑惑解答</h3>
<div class="sin_text">
    					<h3>问题1：</h3>
<p>程序中出现[WARN] Can not find [logging.properties], use [%JRE_HOME%/lib/logging.properties] as default!这行警告，请问这个问题如何解决？</p>
<h3>答：</h3>
<p>这是因为你没有引入任何第三方日志框架。Hutool默认会使用JDK Logging做为其日志实现。出现这句话的意思是你没有在你的ClassPath下放logging.properties（JDK Logging的配置文件），系统默认默认读取JDK目录下的默认配置文件。你可以考虑在ClassPath下放一个logging.properties（就是src/main/resources）下。</p>
<p>配置文件模板见：https://github.com/looly/hutool/blob/master/hutool-log/src/test/resources/logging.properties</p>
<p>其它日志框架的配置文件模板见：https://github.com/looly/hutool/tree/master/hutool-log/src/test/resources</p> 
<hr>
			</div>

																														<h2 id="category_52946">缓存(Hutool-cache)</h2>
																								<h3 id="text_215999" class="sin_target">概述</h3>
<div class="sin_text">
    					<h2>来源</h2>
<p>Hutool-cache模块最早受到jodd-cache的启发（如今大部分逻辑依旧与jodd保持一致），此模块提供一种缓存的简单实现方案，在小型项目中对于简单的缓存需求非常好用。</p>
<h2>介绍</h2>
<p>Hutoo-cache模块提供了几种缓存策略实现：</p>
<h3>FIFOCache</h3>
<p>FIFO(first in first out) 先进先出策略。元素不停的加入缓存直到缓存满为止，当缓存满时，清理过期缓存对象，清理后依旧满则删除先入的缓存（链表首部对象）。</p>
<p>优点：简单快速<br>缺点：不灵活，不能保证最常用的对象总是被保留</p>
<h3>LFUCache</h3>
<p>LFU(least frequently used) 最少使用率策略。根据使用次数来判定对象是否被持续缓存（使用率是通过访问次数计算），当缓存满时清理过期对象，清理后依旧满的情况下清除最少访问（访问计数最小）的对象并将其他对象的访问数减去这个最小访问数，以便新对象进入后可以公平计数。</p>
<h3>LRUCache</h3>
<p>LRU (least recently used)最近最久未使用缓存。根据使用时间来判定对象是否被持续缓存，当对象被访问时放入缓存，当缓存满了，最久未被使用的对象将被移除。此缓存基于LinkedHashMap，因此当被缓存的对象每被访问一次，这个对象的key就到链表头部。这个算法简单并且非常快，他比FIFO有一个显著优势是经常使用的对象不太可能被移除缓存。缺点是当缓存满时，不能被很快的访问。</p>
<h3>TimedCache</h3>
<p>定时缓存，对被缓存的对象定义一个过期时间，当对象超过过期时间会被清理。此缓存没有容量限制，对象只有在过期后才会被移除</p>
<h3>WeakCache</h3>
<p>弱引用缓存。对于一个给定的键，其映射的存在并不阻止垃圾回收器对该键的丢弃，这就使该键成为可终止的，被终止，然后被回收。丢弃某个键时，其条目从映射中有效地移除。该类使用了WeakHashMap做为其实现，缓存的清理依赖于JVM的垃圾回收。</p> 
<hr>
<h3>FileCach</h3>
<p>FileCach是一个独立的缓存，主要是将小文件以byte[]的形式缓存到内容中，减少文件的访问，以解决频繁读取文件引起的性能问题。</p>
<p>主要实现有：</p> 
<ul> 
 <li>LFUFileCache</li> 
 <li>LRUFileCache</li> 
</ul>
			</div>

																						<h3 id="text_216306" class="sin_target">CacheUtil</h3>
<div class="sin_text">
    					<h2>概述</h2>
<p>CacheUtil是缓存创建的快捷工具类。用于快速创建不同的缓存对象。</p>
<h2>使用</h2> 
<pre><code>//新建FIFOCache
Cache&lt;String,String&gt; fifoCache = CacheUtil.newFIFOCache(3);
</code></pre>
<p>同样其它类型的Cache也可以调用newXXX的方法创建。</p>
			</div>

																						<h3 id="text_217530" class="sin_target">FIFOCache</h3>
<div class="sin_text">
    					<h2>介绍</h2>
<p>FIFO(first in first out) 先进先出策略。元素不停的加入缓存直到缓存满为止，当缓存满时，清理过期缓存对象，清理后依旧满则删除先入的缓存（链表首部对象）。</p>
<p>优点：简单快速<br>缺点：不灵活，不能保证最常用的对象总是被保留</p>
<h2>使用</h2> 
<pre><code>Cache&lt;String,String&gt; fifoCache = CacheUtil.newFIFOCache(3);

//加入元素，每个元素可以设置其过期时长，DateUnit.SECOND.getMillis()代表每秒对应的毫秒数，在此为3秒
fifoCache.put("key1", "value1", DateUnit.SECOND.getMillis() * 3);
fifoCache.put("key2", "value2", DateUnit.SECOND.getMillis() * 3);
fifoCache.put("key3", "value3", DateUnit.SECOND.getMillis() * 3);

//由于缓存容量只有3，当加入第四个元素的时候，根据FIFO规则，最先放入的对象将被移除
fifoCache.put("key4", "value4", DateUnit.SECOND.getMillis() * 3);

//value1为null
String value1 = fifoCache.get("key1");
</code></pre>
			</div>

																						<h3 id="text_217737" class="sin_target">LFUCache</h3>
<div class="sin_text">
    					<h2>介绍</h2>
<p>LFU(least frequently used) 最少使用率策略。根据使用次数来判定对象是否被持续缓存（使用率是通过访问次数计算），当缓存满时清理过期对象，清理后依旧满的情况下清除最少访问（访问计数最小）的对象并将其他对象的访问数减去这个最小访问数，以便新对象进入后可以公平计数。</p>
<h2>使用</h2> 
<pre><code>Cache&lt;String, String&gt; lfuCache = CacheUtil.newLFUCache(3);
//通过实例化对象创建
//LFUCache&lt;String, String&gt; lfuCache = new LFUCache&lt;String, String&gt;(3);

lfuCache.put("key1", "value1", DateUnit.SECOND.getMillis() * 3);
lfuCache.get("key1");//使用次数+1
lfuCache.put("key2", "value2", DateUnit.SECOND.getMillis() * 3);
lfuCache.put("key3", "value3", DateUnit.SECOND.getMillis() * 3);
lfuCache.put("key4", "value4", DateUnit.SECOND.getMillis() * 3);

//由于缓存容量只有3，当加入第四个元素的时候，根据LRU规则，最少使用的将被移除（2,3被移除）
String value2 = lfuCache.get("key2");//null
String value3 = lfuCache.get("key3");//null
</code></pre>
			</div>

																						<h3 id="text_217738" class="sin_target">LRUCache</h3>
<div class="sin_text">
    					<h2>介绍</h2>
<p>LRU (least recently used)最近最久未使用缓存。根据使用时间来判定对象是否被持续缓存，当对象被访问时放入缓存，当缓存满了，最久未被使用的对象将被移除。此缓存基于LinkedHashMap，因此当被缓存的对象每被访问一次，这个对象的key就到链表头部。这个算法简单并且非常快，他比FIFO有一个显著优势是经常使用的对象不太可能被移除缓存。缺点是当缓存满时，不能被很快的访问。</p>
<h2>使用</h2> 
<pre><code>Cache&lt;String, String&gt; lruCache = CacheUtil.newLRUCache(3);
//通过实例化对象创建
//LRUCache&lt;String, String&gt; lruCache = new LRUCache&lt;String, String&gt;(3);
lruCache.put("key1", "value1", DateUnit.SECOND.getMillis() * 3);
lruCache.put("key2", "value2", DateUnit.SECOND.getMillis() * 3);
lruCache.put("key3", "value3", DateUnit.SECOND.getMillis() * 3);
lruCache.get("key1");//使用时间推近
lruCache.put("key4", "value4", DateUnit.SECOND.getMillis() * 3);

//由于缓存容量只有3，当加入第四个元素的时候，根据LRU规则，最少使用的将被移除（2被移除）
String value2 = lruCache.get("key");//null
</code></pre>
			</div>

																						<h3 id="text_217741" class="sin_target">TimedCache</h3>
<div class="sin_text">
    					<h2>介绍</h2>
<p>定时缓存，对被缓存的对象定义一个过期时间，当对象超过过期时间会被清理。此缓存没有容量限制，对象只有在过期后才会被移除。</p>
<h2>使用</h2> 
<pre><code>TimedCache&lt;String, String&gt; timedCache = (TimedCache)CacheUtil.newTimedCache(DateUnit.SECOND.getMillis() * 3);
//实例化创建
//TimedCache&lt;String, String&gt; timedCache = new TimedCache&lt;String, String&gt;(DateUnit.SECOND.getMillis() * 3);
timedCache.put("key1", "value1", 1);
timedCache.put("key2", "value2", DateUnit.SECOND.getMillis() * 5);

//启动定时任务，每5毫秒秒检查一次过期
timedCache.schedulePrune(5);

//等待5毫秒
ThreadUtil.sleep(5);

//5毫秒后由于value2设置了5毫秒过期，因此只有value2被保留下来
String value1 = timedCache.get("key1");//null
String value2 = timedCache.get("key2");//null

//取消定时清理
timedCache.cancelPruneSchedule();
</code></pre>
			</div>

																						<h3 id="text_217745" class="sin_target">WeakCache</h3>
<div class="sin_text">
    					<h2>介绍</h2>
<p>弱引用缓存。对于一个给定的键，其映射的存在并不阻止垃圾回收器对该键的丢弃，这就使该键成为可终止的，被终止，然后被回收。丢弃某个键时，其条目从映射中有效地移除。该类使用了WeakHashMap做为其实现，缓存的清理依赖于JVM的垃圾回收。</p>
<h2>使用</h2>
<p>与TimedCache使用方法一致：</p> 
<pre><code>WeakCache&lt;String, String&gt; weakCache = CacheUtil.newWeakCache(DateUnit.SECOND.getMillis() * 3);
</code></pre>
<p>WeakCache也可以像TimedCache一样设置定时清理时间，同时具备垃圾回收清理。</p>
			</div>

																						<h3 id="text_217754" class="sin_target">FileCache</h3>
<div class="sin_text">
    					<h2>介绍</h2>
<p>FileCache主要是将小文件以byte[]的形式缓存到内容中，减少文件的访问，以解决频繁读取文件引起的性能问题。</p>
<h2>实现</h2> 
<ul> 
 <li>LFUFileCache</li> 
 <li>LRUFileCache</li> 
</ul>
<h2>使用</h2> 
<pre><code>//参数1：容量，能容纳的byte数
//参数2：最大文件大小，byte数，决定能缓存至少多少文件，大于这个值不被缓存直接读取
//参数3：超时。毫秒
LFUFileCache cache = new LFUFileCache(1000, 500, 2000);
byte[] bytes = cache.getFileBytes("d:/a.jpg");
</code></pre>
<p>LRUFileCache的使用与LFUFileCache一致，不再举例。</p>
			</div>

																														<h2 id="category_52947">JSON(Hutool-json)</h2>
																								<h3 id="text_216000" class="sin_target">概述</h3>
<div class="sin_text">
    					<h2>Hutool-json</h2> 
<hr>
<h2>为何集成</h2>
<p>JSON在现在的开发中做为跨平台的数据交换格式已经慢慢有替代XML的趋势（比如RestFul规范），我想大家在开发中对外提供接口也越来越多的使用JSON格式。</p>
<p>不可否认，现在优秀的JSON框架非常多，我经常使用的像阿里的FastJSON，Jackson等都是非常优秀的包，性能突出，简单易用。Hutool开始也并不想自己写一个JSON，但是在各种工具的封装中，发现JSON已经不可或缺，因此将json.org官方的JSON解析纳入其中，进行改造。在改造过程中，积极吸取其它类库优点，优化成员方法，抽象接口和类，最终形成Hutool-json。</p>
<h2>介绍</h2>
<p>Hutool-json的核心类只有两个：<br>- JSONObject<br>- JSONArray<br>这与其它JSON包是类似的，与此同时，还提供一个<code>JSONUtil</code>工具类用于简化针对JSON的各种操作和转换。</p>
<p>除了核心类，还提供了一些辅助类用于实现特定功能：<br>- JSONSupport Bean类继承此对象即可无缝转换为JSON或JSON字符串。同时实现了<code>toString()</code>方法可将当前对象输出为JSON字符串。<br>- XML 提供JSON与XML之间的快速转换，同时<code>JSONUtil</code>中有相应静态封装。<br>- JSON JSONObject和JSONArray共同实现的接口类，JSONUtil.parse方法默认返回此对象（因为不知道是JSON对象还是JSON数组），然后可以根据实际类型判断后转换对象类型。</p>
<p>与FastJSON类似，JSONObject实现了Map接口，JSONArray实现了List接口，这样我们便可以使用熟悉的API来操作JSON。</p>
<p>在JSON中，Hutool封装了getXXX方法，支持大部分内置类型的值获取操作。比如：</p> 
<pre><code>JSONObject json1 = JSONUtil.createObj();
json1.getStr("key");
json1.getInt("key");
json1.getLong("key");
json1.getDouble("key");
json1.getBigDecimal("key");
</code></pre>
<p>这些成员方法的加入，可以省掉大量的类型转换代码，大大提高JSON的操作简便性。</p>
			</div>

																						<h3 id="text_216001" class="sin_target">JSONObject</h3>
<div class="sin_text">
    					<h2>介绍</h2>
<p>JSONObject代表一个JSON中的键值对象，这个对象以大括号包围，每个键值对使用<code>,</code>隔开，键与值使用<code>:</code>隔开，一个JSONObject类似于这样：<br> </p>
<pre><code><br>{
  "key1":"value1",
  "key2":"value2"
}
</code></pre>
<p></p>
<p>此处键部分可以省略双引号，值为字符串时不能省略，为数字或波尔值时不加双引号。</p>
<h2>使用</h2>
<h3>创建</h3> 
<pre><code>JSONObject json1 = JSONUtil.createObj();
json1.put("a", "value1");
json1.put("b", "value2");
json1.put("c", "value3");
</code></pre>
<p><code>JSONUtil.createObj()</code>是快捷新建JSONObject的工具方法，同样我们可以直接new：</p> 
<pre><code>JSONObject json1 = new JSONObject();
</code></pre>
<h3>转换</h3> 
<pre><code>String jsonStr = "{\"b\":\"value2\",\"c\":\"value3\",\"a\":\"value1\"}";
//方法一：使用工具类转换
JSONObject jsonObject = JSONUtil.parseObj(jsonStr);
//方法二：new的方式转换
JSONObject jsonObject2 = new JSONObject(jsonStr);

//JSON对象转字符串
jsonObject.toString();
</code></pre>
<p>同样，<code>JSONUtil</code>还可以支持以下对象转为JSONObject对象：<br>- String对象<br>- Java Bean对象<br>- Map对象<br>- XML字符串（使用<code>JSONUtil.parseFromXml</code>方法）<br>- ResourceBundle(使用<code>JSONUtil.parseFromResourceBundle</code>)</p>
<p><code>JSONUtil</code>还提供了JSONObject对象转换为其它对象的方法：<br>- toJsonStr 转换为JSON字符串<br>- toXmlStr 转换为XML字符串<br>- toBean 转换为JavaBean<br>- </p>
			</div>

																						<h3 id="text_216002" class="sin_target">JSONArray</h3>
<div class="sin_text">
    					<h2>介绍</h2>
<p>在JSON中，JSONArray代表一个数组，使用中括号包围，每个元素使用逗号隔开。一个JSONArray类似于这样：<br> </p>
<pre><code><br>["value1","value2","value3"]
</code></pre>
<p></p>
<h2>使用</h2>
<h3>创建</h3> 
<pre><code>//方法1
JSONArray array = JSONUtil.createArray();
//方法2
JSONArray array = new JSONArray();

array.add("value1");
array.add("value2");
array.add("value3");

//转为JSONArray字符串
array.toString();
</code></pre>
<h3>转换</h3> 
<pre><code>String jsonStr = "[\"value1\", \"value2\", \"value3\"]";
JSONArray array = JSONUtil.parseArray(jsonStr);
</code></pre>
			</div>

																						<h3 id="text_216003" class="sin_target">JSONUtil</h3>
<div class="sin_text">
    					<h2>介绍</h2>
<p><code>JSONUtil</code>是针对JSONObject和JSONArray的静态快捷方法集合，在之前的章节我们已经介绍了一些工具方法，在本章节我们将做一些补充。</p>
<h2>使用</h2>
<h3>parseXXX和toXXX</h3>
<p>这两种方法主要是针对JSON和其它对象之间的转换。</p>
<h3>readXXX</h3>
<p>这类方法主要是从JSON文件中读取JSON对象的快捷方法。包括：<br>- readJSON<br>- readJSONObject<br>- readJSONArray</p>
<h3>其它方法</h3>
<p>除了上面中常用的一些方法，JSONUtil还提供了一些JSON辅助方法：<br>- quote 对所有双引号做转义处理（使用双反斜杠做转义）<br>- wrap 包装对象，可以将普通任意对象转为JSON对象</p>
			</div>

																														<h2 id="category_52948">加密解密(Hutool-crypto)</h2>
																								<h3 id="text_216004" class="sin_target">概述</h3>
<div class="sin_text">
    					<h2>Hutool-crypto概述</h2>
<p>加密分为三种：<br>1. 对称加密（symmetric），例如：AES、DES等<br>2. 非对称加密（asymmetric），例如：RSA、DSA等<br>3. 摘要加密（digest），例如：MD5、SHA-1、SHA-256、HMAC等</p>
<p>hutool-crypto针对这三种加密类型分别封装，并提供常用的大部分加密算法。</p>
<p>对于非对称加密，实现了：<br>- RSA<br>- DSA</p>
<p>对于对称加密，实现了：<br>- AES<br>- ARCFOUR<br>- Blowfish<br>- DES<br>- DESede<br>- RC2<br>- PBEWithMD5AndDES<br>- PBEWithSHA1AndDESede<br>- PBEWithSHA1AndRC2_40</p>
<p>对于摘要算法实现了：<br>- MD2<br>- MD5<br>- SHA-1<br>- SHA-256<br>- SHA-384<br>- SHA-512<br>- HmacMD5<br>- HmacSHA1<br>- HmacSHA256<br>- HmacSHA384<br>- HmacSHA512</p>
<p>其中，针对常用到的算法，模块还提供<code>SecureUtil</code>工具类用于快速实现加密。</p>
<p>关于各种加密方式的使用，请参阅后续章节。</p>
			</div>

																						<h3 id="text_216005" class="sin_target">对称加密-SymmetricCrypto</h3>
<div class="sin_text">
    					<h2>介绍</h2>
<p>对称加密(也叫私钥加密)指加密和解密使用相同密钥的加密算法。有时又叫传统密码算法，就是加密密钥能够从解密密钥中推算出来，同时解密密钥也可以从加密密钥中推算出来。而在大多数的对称算法中，加密密钥和解密密钥是相同的，所以也称这种加密算法为秘密密钥算法或单密钥算法。它要求发送方和接收方在安全通信之前，商定一个密钥。对称算法的安全性依赖于密钥，泄漏密钥就意味着任何人都可以对他们发送或接收的消息解密，所以密钥的保密性对通信的安全性至关重要。</p>
<p>对于对称加密，实现了：<br>- AES<br>- ARCFOUR<br>- Blowfish<br>- DES<br>- DESede<br>- RC2<br>- PBEWithMD5AndDES<br>- PBEWithSHA1AndDESede<br>- PBEWithSHA1AndRC2_40</p>
<h2>使用</h2>
<p>以AES算法为例：</p> 
<pre><code>String content = "test中文";

//随机生成密钥
byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue()).getEncoded();

//构建
SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, key);
//加密
byte[] encrypt = aes.encrypt(content);
//解密
byte[] decrypt = aes.decrypt(encrypt);

//Junit单元测试
Assert.assertEquals(content, StrUtil.str(decrypt, CharsetUtil.CHARSET_UTF_8));

//加密为16进制表示
String encryptHex = aes.encryptHex(content);
//解密为字符串
String decryptStr = aes.decryptStr(encryptHex, CharsetUtil.CHARSET_UTF_8);

//Junit单元测试
Assert.assertEquals(content, decryptStr);
</code></pre>
			</div>

																						<h3 id="text_216006" class="sin_target">非对称加密-AsymmetricCrypto</h3>
<div class="sin_text">
    					<h2>介绍</h2>
<p>对于非对称加密，最常用的就是RSA和DSA，在Hutool中使用<code>AsymmetricCrypto</code>对象来负责加密解密。</p>
<p>非对称加密有公钥和私钥两个概念，私钥自己拥有，不能给别人，公钥公开。根据应用的不同，我们可以选择使用不同的密钥加密：</p> 
<ol> 
 <li><p>签名：使用私钥加密，公钥解密。用于让所有公钥所有者验证私钥所有者的身份并且用来防止私钥所有者发布的内容被篡改，但是不用来保证内容不被他人获得。</p></li> 
 <li><p>加密：用公钥加密，私钥解密。用于向公钥所有者发布信息,这个信息可能被他人篡改,但是无法被他人获得。</p></li> 
</ol>
<h2>使用</h2>
<p>在非对称加密中，我们可以通过<code>AsymmetricCrypto(AsymmetricAlgorithm algorithm)</code>构造方法，通过传入不同的算法枚举，获得其加密解密器。</p>
<p>当然，为了方便，我们针对最常用的RSA和DSA算法构建了单独的对象：<code>RSA</code>和<code>DSA</code>。</p>
<h3>基本使用</h3>
<p>我们以RSA为例，介绍使用RSA加密和解密 在构建RSA对象时，可以传入公钥或私钥，当使用无参构造方法时，Hutool将自动生成随机的公钥私钥密钥对：</p> 
<pre><code>RSA rsa = new RSA();

//获得私钥
rsa.getPrivateKey()
rsa.getPrivateKeyBase64()
//获得公钥
rsa.getPublicKey()
rsa.getPublicKeyBase64()

//公钥加密，私钥解密
byte[] encrypt = rsa.encrypt(StrUtil.bytes("我是一段测试aaaa", CharsetUtil.CHARSET_UTF_8), KeyType.PublicKey);
byte[] decrypt = rsa.decrypt(encrypt, KeyType.PrivateKey);
Assert.assertEquals("我是一段测试aaaa", StrUtil.str(decrypt, CharsetUtil.CHARSET_UTF_8));

//私钥加密，公钥解密
byte[] encrypt2 = rsa.encrypt(StrUtil.bytes("我是一段测试aaaa", CharsetUtil.CHARSET_UTF_8), KeyType.PrivateKey);
byte[] decrypt2 = rsa.decrypt(encrypt2, KeyType.PublicKey);
Assert.assertEquals("我是一段测试aaaa", StrUtil.str(decrypt2, CharsetUtil.CHARSET_UTF_8));
</code></pre> 
<blockquote>
 <p>对于加密和解密可以完全分开，对于RSA对象，如果只使用公钥或私钥，另一个参数可以为<code>null</code></p> 
</blockquote>
<h3>自助生成密钥对</h3>
<p>有时候我们想自助生成密钥对可以：</p> 
<pre><code>KeyPair pair = SecureUtil.generateKeyPair("RSA");
pair.getPrivate();
pair.getPublic();
</code></pre>
<p>自助生成的密钥对是byte[]形式，我们可以使用<code>Base64.encode</code>方法转为Base64，便于存储为文本。</p>
<p>当然，如果使用<code>RSA</code>对象，也可以使用<code>encryptStr</code>和<code>decryptStr</code>加密解密为字符串</p>
<h2>案例</h2>
<h3>案例一：</h3>
<p>已知私钥和密文，如何解密密文？</p> 
<pre><code>String PRIVATE_KEY = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAIL7pbQ+5KKGYRhw7jE31hmA"
		+ "f8Q60ybd+xZuRmuO5kOFBRqXGxKTQ9TfQI+aMW+0lw/kibKzaD/EKV91107xE384qOy6IcuBfaR5lv39OcoqNZ"
		+ "5l+Dah5ABGnVkBP9fKOFhPgghBknTRo0/rZFGI6Q1UHXb+4atP++LNFlDymJcPAgMBAAECgYBammGb1alndta"
		+ "xBmTtLLdveoBmp14p04D8mhkiC33iFKBcLUvvxGg2Vpuc+cbagyu/NZG+R/WDrlgEDUp6861M5BeFN0L9O4hz"
		+ "GAEn8xyTE96f8sh4VlRmBOvVdwZqRO+ilkOM96+KL88A9RKdp8V2tna7TM6oI3LHDyf/JBoXaQJBAMcVN7fKlYP"
		+ "Skzfh/yZzW2fmC0ZNg/qaW8Oa/wfDxlWjgnS0p/EKWZ8BxjR/d199L3i/KMaGdfpaWbYZLvYENqUCQQCobjsuCW"
		+ "nlZhcWajjzpsSuy8/bICVEpUax1fUZ58Mq69CQXfaZemD9Ar4omzuEAAs2/uee3kt3AvCBaeq05NyjAkBme8SwB0iK"
		+ "kLcaeGuJlq7CQIkjSrobIqUEf+CzVZPe+AorG+isS+Cw2w/2bHu+G0p5xSYvdH59P0+ZT0N+f9LFAkA6v3Ae56OrI"
		+ "wfMhrJksfeKbIaMjNLS9b8JynIaXg9iCiyOHmgkMl5gAbPoH/ULXqSKwzBw5mJ2GW1gBlyaSfV3AkA/RJC+adIjsRGg"
		+ "JOkiRjSmPpGv3FOhl9fsBPjupZBEIuoMWOC8GXK/73DHxwmfNmN7C9+sIi4RBcjEeQ5F5FHZ";

RSA rsa = new RSA(PRIVATE_KEY, null);

String a = "2707F9FD4288CEF302C972058712F24A5F3EC62C5A14AD2FC59DAB93503AA0FA17113A020EE4EA35EB53F"
		+ "75F36564BA1DABAA20F3B90FD39315C30E68FE8A1803B36C29029B23EB612C06ACF3A34BE815074F5EB5AA3A"
		+ "C0C8832EC42DA725B4E1C38EF4EA1B85904F8B10B2D62EA782B813229F9090E6F7394E42E6F44494BB8";

byte[] aByte = HexUtil.decodeHex(a);
byte[] decrypt = rsa.decrypt(aByte, KeyType.PrivateKey);
Assert.assertEquals("虎头闯杭州,多抬头看天,切勿只管种地", StrUtil.str(decrypt, CharsetUtil.CHARSET_UTF_8));
</code></pre>
			</div>

																						<h3 id="text_216007" class="sin_target">摘要加密-Digester和HMac</h3>
<div class="sin_text">
    					<h2>介绍</h2>
<h3>摘要算法介绍</h3>
<p>摘要算法是一种能产生特殊输出格式的算法，这种算法的特点是：无论用户输入什么长度的原始数据，经过计算后输出的密文都是固定长度的，这种算法的原理是根据一定的运算规则对原数据进行某种形式的提取，这种提取就是摘要，被摘要的数据内容与原数据有密切联系，只要原数据稍有改变，输出的“摘要”便完全不同，因此，基于这种原理的算法便能对数据完整性提供较为健全的保障。</p>
<p>但是，由于输出的密文是提取原数据经过处理的定长值，所以它已经不能还原为原数据，即消息摘要算法是不可逆的，理论上无法通过反向运算取得原数据内容，因此它通常只能被用来做数据完整性验证。</p>
<h3>HMAC介绍</h3>
<p>HMAC，全称为“Hash Message Authentication Code”，中文名“散列消息鉴别码”，主要是利用哈希算法，以一个密钥和一个消息为输入，生成一个消息摘要作为输出。一般的，消息鉴别码用于验证传输于两个共 同享有一个密钥的单位之间的消息。HMAC 可以与任何迭代散列函数捆绑使用。MD5 和 SHA-1 就是这种散列函数。HMAC 还可以使用一个用于计算和确认消息鉴别值的密钥。</p>
<h2>Hutool支持的摘要算法类型</h2>
<h3>摘要算法</h3> 
<ul> 
 <li>MD2</li> 
 <li>MD5</li> 
 <li>SHA-1</li> 
 <li>SHA-256</li> 
 <li>SHA-384</li> 
 <li>SHA-512</li> 
</ul>
<h3>Hmac算法</h3> 
<ul> 
 <li>HmacMD5</li> 
 <li>HmacSHA1</li> 
 <li>HmacSHA256</li> 
 <li>HmacSHA384</li> 
 <li>HmacSHA512</li> 
</ul>
<h2>摘要算法抽象</h2>
<p>摘要对象被抽象为两个对象：<br>- Digester<br>- HMac</p>
<h2>使用</h2>
<h3>Digester</h3>
<p>以MD5为例：<br> </p>
<pre><code><br>Digester md5 = new Digester(DigestAlgorithm.MD5);
String digestHex = md5.digestHex(testStr);
Assert.assertEquals("5393554e94bf0eb6436f240a4fd71282", digestHex);
</code></pre>
<p></p>
<p>当然，做为最为常用的方法，MD5等方法被封装为工具方法在<code>DigestUtil</code>中，以上代码可以进一步简化为：<br> </p>
<pre><code><br>String md5Hex1 = DigestUtil.md5Hex(testStr);
Assert.assertEquals("5393554e94bf0eb6436f240a4fd71282", md5Hex1);
</code></pre>
<p></p>
<h3>HMac</h3>
<p>以HmacMD5为例：<br> </p>
<pre><code><br>String testStr = "test中文";
		
byte[] key = "password".getBytes();
HMac mac = new HMac(HmacAlgorithm.HmacMD5, key);

String macHex1 = mac.digestHex(testStr);
Assert.assertEquals("b977f4b13f93f549e06140971bded384", macHex1);
</code></pre>
<p></p>
			</div>

																						<h3 id="text_216008" class="sin_target">加密解密工具-SecureUtil</h3>
<div class="sin_text">
    					<h2>介绍</h2>
<p><code>SecureUtil</code>主要针对常用加密算法构建快捷方式，还有提供一些密钥生成的快捷工具方法。</p>
<h2>方法介绍</h2>
<h3>对称加密</h3> 
<ul> 
 <li><code>SecureUtil.aes</code></li> 
 <li><code>SecureUtil.des</code></li> 
</ul>
<h3>摘要算法</h3> 
<ul> 
 <li><code>SecureUtil.md5</code></li> 
 <li><code>SecureUtil.sha1</code></li> 
 <li><code>SecureUtil.hmac</code></li> 
 <li><code>SecureUtil.hmacMd5</code></li> 
 <li><code>SecureUtil.hmacSha1</code></li> 
</ul>
<h3>非对称加密</h3> 
<ul> 
 <li><code>SecureUtil.rsa</code></li> 
 <li><code>SecureUtil.dsa</code></li> 
</ul>
<h3>UUID</h3> 
<ul> 
 <li><code>SecureUtil.simpleUUID</code> 方法提供无“-”的UUID</li> 
</ul>
<h3>密钥生成</h3> 
<ul> 
 <li><code>SecureUtil.generateKey</code> 针对对称加密生成密钥</li> 
 <li><code>SecureUtil.generateKeyPair</code> 生成密钥对（用于非对称加密）</li> 
 <li><code>SecureUtil.generateSignature</code> 生成签名（用于非对称加密）</li> 
</ul>
<p>其它方法为针对特定加密方法的一些密钥生成和签名相关方法，详细请参阅API文档。</p>
			</div>

																														<h2 id="category_52949">DFA查找(Hutool-dfa)</h2>
																								<h3 id="text_216009" class="sin_target">概述</h3>
<div class="sin_text">
    					<h2>由来</h2>
<p>在我最早入职的一家公司，主要负责内容方面的业务，对我来说大部分的工作是对内容的清洗和规整。当然，清洗过程免不了的就是按照关键词过滤，你懂的。需求如下：</p>
<p>后台人员添加N个关键字，然后对主站所有的内容进行清洗，含有这些关键字的所有内容都置为无效。</p>
<h3>思路</h3>
<p>拿到此需求，我最早的方案比较粗暴：针对关键字建立一个HashSet，然后遍历整个数据库，针对每篇文章遍历这个Set，查找是否contains关键字……好吧我承认这不是一个好方法，随着关键字的增多和数据的增多，这个过程消耗的时间成指数型增长！</p>
<p>于是我找到度娘，发现一个算法：DFA。</p>
<h2>DFA介绍</h2>
<p>DFA全称为：Deterministic Finite Automaton,即确定有穷自动机。因为本人算法学的不好，有兴趣的可以看这篇博客: <a href="http://www.cnblogs.com/naaoveGIS/archive/2016/10/14/5960352.html" rel="nofollow">基于DFA敏感词查询的算法简析</a> </p>
<p>解释起来原理其实也不难，就是用所有关键字构造一棵树，然后用正文遍历这棵树，遍历到叶子节点即表示文章中存在这个关键字。</p>
<p>我们暂且忽略构建关键词树的时间，每次查找正文只需要O(n)复杂度就可以搞定。</p>
<p>针对DFA算法以及网上的一些实现，Hutool做了整理和改进，最终形成现在的Hutool-dfa模块。</p>
			</div>

																						<h3 id="text_216010" class="sin_target">DFA查找</h3>
<div class="sin_text">
    					<h2>使用</h2>
<h3>1. 构建关键词树</h3> 
<pre><code>WordTree tree = new WordTree();
tree.addWord("大");
tree.addWord("大土豆");
tree.addWord("土豆");
tree.addWord("刚出锅");
tree.addWord("出锅");
</code></pre>
<h3>2. 查找关键词</h3> 
<pre><code>//正文
String text = "我有一颗大土豆，刚出锅的";
</code></pre> 
<ol> 
 <li>情况一：标准匹配，匹配到最短关键词，并跳过已经匹配的关键词</li> 
</ol> 
<pre><code>// 匹配到【大】，就不再继续匹配了，因此【大土豆】不匹配
// 匹配到【刚出锅】，就跳过这三个字了，因此【出锅】不匹配（由于刚首先被匹配，因此长的被匹配，最短匹配只针对第一个字相同选最短）
List&lt;String&gt; matchAll = tree.matchAll(text, -1, false, false);
Assert.assertEquals(matchAll.toString(), "[大, 土豆, 刚出锅]");
</code></pre> 
<ol> 
 <li>情况二：匹配到最短关键词，不跳过已经匹配的关键词</li> 
</ol> 
<pre><code>// 【大】被匹配，最短匹配原则【大土豆】被跳过，【土豆继续被匹配】
// 【刚出锅】被匹配，由于不跳过已经匹配的词，【出锅】被匹配
matchAll = tree.matchAll(text, -1, true, false);
Assert.assertEquals(matchAll.toString(), "[大, 土豆, 刚出锅, 出锅]");
</code></pre> 
<ol> 
 <li>情况三：匹配到最长关键词，跳过已经匹配的关键词</li> 
</ol> 
<pre><code>// 匹配到【大】，由于到最长匹配，因此【大土豆】接着被匹配
// 由于【大土豆】被匹配，【土豆】被跳过，由于【刚出锅】被匹配，【出锅】被跳过
matchAll = tree.matchAll(text, -1, false, true);
Assert.assertEquals(matchAll.toString(), "[大, 大土豆, 刚出锅]");
</code></pre> 
<ol> 
 <li>情况四：匹配到最长关键词，不跳过已经匹配的关键词（最全关键词）</li> 
</ol> 
<pre><code>// 匹配到【大】，由于到最长匹配，因此【大土豆】接着被匹配，由于不跳过已经匹配的关键词，土豆继续被匹配
// 【刚出锅】被匹配，由于不跳过已经匹配的词，【出锅】被匹配
matchAll = tree.matchAll(text, -1, true, true);
Assert.assertEquals(matchAll.toString(), "[大, 大土豆, 土豆, 刚出锅, 出锅]");
</code></pre> 
<blockquote>
 <p>除了<code>matchAll</code>方法，<code>WordTree</code>还提供了<code>match</code>和<code>isMatch</code>两个方法，这两个方法只会查找第一个匹配的结果，这样一旦找到第一个关键字，就会停止继续匹配，大大提高了匹配效率。</p> 
</blockquote>
<h2>针对特殊字符</h2>
<p>有时候，正文中的关键字常常包含特殊字符，比如:“〓关键☆字”，针对这种情况，Hutool提供了<code>StopChar</code>类，专门针对特殊字符做跳过处理，这个过程是在<code>match</code>方法或<code>matchAll</code>方法执行的时候自动去掉特殊字符。</p>
			</div>

																														<h2 id="category_52950">数据库(Hutool-db)</h2>
																								<h3 id="text_216011" class="sin_target">概述</h3>
<div class="sin_text">
    					<h3>开篇</h3>
<p>Hutool-db最早只是一个简单的SQL运行器，现在加入了方言支持，封装了增删改查，引入Session从而支持事务，可以说工程量巨大，在封装过程总我还是参考了<a href="http://jodd.org/" rel="nofollow">Jodd</a>的DbOom、<a href="http://www.jfinal.com/" rel="nofollow">Jfinal</a>的ActiveRecord、<a href="http://commons.apache.org/proper/commons-dbutils/" rel="nofollow">Apache Commons-DbUtils</a>，吸取优点，剔除我觉得没用的，再加入些自己的想法，尽量做到简单和灵活。</p>
<h3>由来</h3>
<p>考虑到<a href="http://hibernate.org/orm/" rel="nofollow">Hibernate</a>做ORM的复杂性，它想把一个对象映射到数据库，再加上各种外键的复杂对应关系，当时学习的时候整的我焦头烂额，而且其数据库连接配置全部放在xml里，需要连接池插件去为它开发对应的插件，显然这样做太霸道了，总之这种灵活性的缺失，导致在使用<a href="http://hibernate.org/orm/" rel="nofollow">Hibernate</a>的时候必须按照它指定的思路和方式走，痛苦万分啊，例如你执行一条SQL语句，查询的结果是个让人百思不得其解的列表，难用的要死。后来我便倾向于<a href="http://commons.apache.org/proper/commons-dbutils/" rel="nofollow">Apache Commons-DbUtils</a>，谁让业务简单呢，没几张表，简简的看了其源码，做了些简单的改进放到我的<a href="https://github.com/looly/hutool" rel="nofollow">Hutool</a>里来了，然后就看了<a href="http://www.jfinal.com/" rel="nofollow">Jfinal</a>的ActiveRecord，那段时间正在看Python的一个框架<a href="https://www.djangoproject.com/" rel="nofollow">Django</a>，发现其异曲同工之妙的ORM方式：将数据库表映射为一个Map，而不是一个对象。这样的灵活性大大的增加，字段也更加灵活。于是按照这个思想开始动工，封装增删改查的常用方法。</p>
<h3>对象解释</h3>
<h4>1. Entity</h4>
<p>在ORM中，我把一张表中的一条数据映射成为一个叫做Entity的类，继承自HashMap，key是字段名，value是Object类型，字段值，这样一个Entity对象就是数据库表中的一条记录，当然这个对象中还有个字段是表的名字，方便之后的操作。之后对数据库增删改查操作的对象大多是这个。</p>
<p>这个对象充当着两种角色，一个是数据的载体，表示一条数据，另一个就是where语句的条件，当然，Entity对象只支持 <code>=</code> 操作，更复杂的操作我以后再想别的办法。充当where条件时，key依旧是字段名，value是字段条件值。例如：<br> </p>
<pre><code><br>Entity where = Entity.create(TABLE_NAME).set("条件1", "条件值");
</code></pre>
<p></p>
<p>表示的where语句是：<br> </p>
<pre><code><br>WHERE `条件1` = 条件值
</code></pre>
<p></p>
<p>当然到时候会用PreparedStatement，不会出现SQL注入。</p>
<h4>2. Table Column</h4>
<p>这两个对象主要是描述数据库表结构的，暂时和ORM本身没啥关系，只是当你想获得一些字段信息的时候，这样来获得表结构信息：</p> 
<pre><code>private static void getTableMetaInfo(DataSource ds) {
	// 获得当前库的所有表的表名
	List&lt;String&gt; tableNames = DbUtil.getTables(ds);
	Log.info("{}", tableNames);

	/*
	 * 获得表结构 表结构封装为一个表对象，里面有Column对象表示一列，列中有列名、类型、大小、是否允许为空等信息
	 */
	Table table = DbUtil.getTableMeta(ds, TABLE_NAME);
	Log.info("{}", table);
}
</code></pre>
<h3>整体的架构</h3>
<p>整体分为几部分</p> 
<ol> 
 <li>数据源 <code>DataSource</code></li> 
 <li>SQL执行器 <code>SqlExecutor</code></li> 
 <li>CRUD的封装 <code>SqlConnRunner</code> <code>SqlRunner</code></li> 
 <li>支持事务的CRUD封装 <code>Session</code></li> 
 <li>各种结果集处理类 <code>handler</code></li> 
 <li>数据库的一些工具方法汇总 <code>DbUtil</code></li> 
</ol>
<p>还有就是没有列出来的dialect（数据库方言），我会根据给定的DataSource、Connection等对象自动识别是什么数据库，然后使用不同的方言构造SQL语句，暂时支持的数据库有MySQL、Oracle、SqlLite3，当然如果识别失败会用ANSI SQL，这样遇到不支持的数据，可以搞定大部分方法。</p>
<p>下面解释下：</p>
<h4>2. SQL执行器 <code>SqlExecutor</code></h4>
<p>这是一个静态类，里面的静态方法只有两种：执行非查询的SQL语句和查询的SQL语句</p> 
<pre><code>/**
 * SqlExecutor样例方法&lt;br&gt;
 * 如果你只是执行SQL语句，使用SqlExecutor类里的静态方法即可
 * 
 * @param ds 数据源
 */
public static void sqlExecutorDemo(DataSource ds) {
	Connection conn = null;
	try {
		conn = ds.getConnection();
		// 执行非查询语句，返回影响的行数
		int count = SqlExecutor.execute(conn, "UPDATE " + TABLE_NAME + " set field1 = ? where id = ?", 0, 0);
		log.info("影响行数：{}", count);
		// 执行非查询语句，返回自增的键，如果有多个自增键，只返回第一个
		Long generatedKey = SqlExecutor.executeForGeneratedKey(conn, "UPDATE " + TABLE_NAME + " set field1 = ? where id = ?", 0, 0);
		log.info("主键：{}", generatedKey);

		/* 执行查询语句，返回实体列表，一个Entity对象表示一行的数据，Entity对象是一个继承自HashMap的对象，存储的key为字段名，value为字段值 */
		List&lt;Entity&gt; entityList = SqlExecutor.query(conn, "select * from " + TABLE_NAME + " where param1 = ?", new EntityHandler(), "值");
		log.info("{}", entityList);
	} catch (SQLException e) {
		Log.error(log, e, "SQL error!");
	} finally {
		DbUtil.close(conn);
	}
}
</code></pre>
<h4>3. CRUD的封装 <code>SqlConnRunner</code> <code>SqlRunner</code></h4>
<p>这两个类有些相似，里面都封装了增、删、改、查、分页、个数方法，差别是<code>SqlConnRunner</code>需要每个方法都传Connection对象，而<code>SqlRunner</code>继承自<code>SqlConnRunner</code>，在传入DataSource会自动获取Connection对象。Demo如下：</p> 
<pre><code>/**
 * SqlRunner是继承自SqlConnRunner的（SqlConnRunner继承自SqlExecutor），所以相应的方法也继承了下来，可以像SqlExecutor一样使用静态方法&lt;br&gt;
 * 当然，SqlRunner更强大的功能在于对Entity对象做CRUD，避免写SQL语句。 SqlRunner需要实例化
 * 
 * SqlRunner同时提供了带Connection参数的CRUD方法，方便外部提供Connection对象而由使用者提供事务的操作
 * 
 * @param ds 数据源
 */
private static void sqlRunnerDemo(DataSource ds) {
	Entity entity = Entity.create(TABLE_NAME).set("字段1", "值").set("字段2", 2);
	Entity where = Entity.create(TABLE_NAME).set("条件1", "条件值");

	try {
		SqlRunner runner = SqlRunner.create(ds);
		// 指定数据库方言，在此为MySQL
		runner = SqlRunner.create(ds);

		// 增，生成SQL为 INSERT INTO `table_name` SET(`字段1`, `字段2`) VALUES(?,?)
		runner.insert(entity);

		// 删，生成SQL为 DELETE FROM `table_name` WHERE `条件1` = ?
		runner.del(where);

		// 改，生成SQL为 UPDATE `table_name` SET `字段1` = ?, `字段2` = ? WHERE `条件1` = ?
		runner.update(entity, where);

		// 查，生成SQL为 SELECT * FROM `table_name` WHERE WHERE `条件1` = ? 第一个参数为返回的字段列表，如果null则返回所有字段
		List&lt;Entity&gt; entityList = runner.find(null, where, new EntityListHandler());
		log.info("{}", entityList);

		// 分页，注意，ANSI SQL中不支持分页！
		List&lt;Entity&gt; pagedEntityList = runner.page(null, where, 0, 20, new EntityListHandler());
		log.info("{}", pagedEntityList);

		// 满足条件的结果数，生成SQL为 SELECT count(1) FROM `table_name` WHERE WHERE `条件1` = ?
		int count = runner.count(where);
		log.info("count: {}", count);
	} catch (SQLException e) {
		Log.error(log, e, "SQL error!");
	} finally {
	}
}
</code></pre>
<h4>4. 支持事务的CRUD封装 <code>Session</code></h4>
<p><code>Session</code>非常类似于<code>SqlRunner</code>，差别是<code>Session</code>对象中只有一个Connection，所有操作也是用这个Connection，便于事务操作，而<code>SqlRunner</code>每执行一个方法都要从<code>DataSource</code>中去要Connection。样例如下：</p> 
<pre><code>public static void sessionDemo(DataSource ds) {
	Entity entity = Entity.create(TABLE_NAME).set("字段1", "值").set("字段2", 2);
	Entity where = Entity.create(TABLE_NAME).set("条件1", "条件值");

	Session session = Session.create(ds);
	try {
		session.beginTransaction();

		// 增，生成SQL为 INSERT INTO `table_name` SET(`字段1`, `字段2`) VALUES(?,?)
		session.insert(entity);

		// 删，生成SQL为 DELETE FROM `table_name` WHERE `条件1` = ?
		session.del(where);

		// 改，生成SQL为 UPDATE `table_name` SET `字段1` = ?, `字段2` = ? WHERE `条件1` = ?
		session.update(entity, where);

		// 查，生成SQL为 SELECT * FROM `table_name` WHERE WHERE `条件1` = ? 第一个参数为返回的字段列表，如果null则返回所有字段
		List&lt;Entity&gt; entityList = session.find(null, where, new EntityListHandler());
		log.info("{}", entityList);

		// 分页，注意，ANSI SQL中不支持分页！
		List&lt;Entity&gt; pagedEntityList = session.page(null, where, 0, 20, new EntityListHandler());
		log.info("{}", pagedEntityList);

		session.commit();
	} catch (Exception e) {
		session.quietRollback();
	} finally {
		session.close();
	}
}
</code></pre>
<h4>5. 各种结果集处理类 <code>handler</code></h4>
<p>此包中有个叫做<code>RsHandler</code>的接口，传入ResultSet对象，返回什么则在handle方法中自己指定。<br>实现的类有：</p> 
<ol> 
 <li>EntityListHandler 转换为Entity列表</li> 
 <li>NumberHandler 当使用<code>select count(1)</code>这类语句的时候，或者返回只有一个结果，且为数字结果的时候，用这个handler</li> 
 <li>EntityHandler 返回一条记录的时候用这个</li> 
 <li><p>数据库的一些工具方法汇总 <code>DbUtil</code><br>提供一些工具方法，最常用的就是<code>close</code>方法了，由于JDK7才把<code>ResultSet``Statement``PreparedStatement``Connection</code>这几个接口实现了Closeable接口，所以之前只能判断类型再去关闭，这样一个close方法可以关闭多个对象。</p></li> 
</ol>
			</div>

																						<h3 id="text_216012" class="sin_target">数据源工厂-DsFactory</h3>
<div class="sin_text">
    					<h2>释义</h2>
<p>数据源（DataSource）的概念来自于JDBC规范中，一个数据源表示针对一个数据库（或者集群）的描述，从数据源中我们可以获得N个数据库连接，从而对数据库进行操作。</p>
<p>每一个开源JDBC连接池都有对DataSource的实现，比如Druid为DruidDataSource，Hikari为HikariDataSource。但是各大连接池配置各不相同，配置文件也不一样，Hutool的针对常用的连接池做了封装，最大限度简化和提供一致性配置。</p>
<p>Hutool的解决方案是：在ClassPath中使用<code>config/db.setting</code>一个配置文件，配置所有种类连接池的数据源，然后使用<code>DsFactory.get()</code>方法自动识别数据源以及自动注入配置文件中的连接池配置（包括数据库连接配置）。<code>DsFactory</code>通过<code>try</code>的方式按照顺序检测项目中引入的jar包来甄别用户使用的是哪种连接池，从而自动构建相应的数据源。</p>
<p>Hutool支持以下连接池，并按照其顺序检测存在与否：</p> 
<ol> 
 <li>HikariCP</li> 
 <li>Druid</li> 
 <li>Tomcat</li> 
 <li>Dbcp</li> 
 <li>C3p0</li> 
</ol>
<p>在没有引入任何连接池的情况下，Hutool会使用其内置的连接池：Hutool Pooled（简易连接池，不推荐在线上环境使用）。</p>
<h2>基本使用</h2>
<h3>1. 引入连接池的jar</h3>
<p>Hutool不会强依赖于任何第三方库，在Hutool支持的连接池范围内，用户需自行选择自己喜欢的连接池并引入。</p>
<h3>2. 编写配置文件</h3>
<p>Maven项目中，在<code>src/main/resources/config</code>下创建文件<code>db.setting</code>，编写配置文件即可。这个配置文件位置就是Hutool与用户间的一个约定（符合约定大于配置的原则）：</p>
<p>配置文件分为两部分</p>
<h4>1. 基本连接信息</h4> 
<pre><code>## 基本配置信息
# JDBC URL，根据不同的数据库，使用相应的JDBC连接字符串
url = jdbc:mysql://&lt;host&gt;:&lt;port&gt;/&lt;database_name&gt;
# 用户名，此处也可以使用 user 代替
username = 用户名
# 密码，此处也可以使用 pass 代替
password = 密码
# JDBC驱动名，可选（Hutool会自动识别）
driver = com.mysql.jdbc.Driver
</code></pre> 
<blockquote>
 <p>** 小提示 **<br>其中driver是可选的，Hutool会根据url自动加载相应的Driver类。基本连接信息是所有连接池通用的，原则上，只有基本信息就可以成功连接并操作数据库。</p> 
</blockquote>
<h4>2. 连接池特有配置信息</h4>
<p>针对不同的连接池，除了基本信息外的配置都各不相同，Hutool针对不同的连接池封装了其配置项，可以在项目的<code>src/test/resources/example</code>中看到针对不同连接池的配置文件样例。</p>
<p>我们以HikariCP为例：</p> 
<pre><code># 自动提交
autoCommit = true
# 等待连接池分配连接的最大时长（毫秒），超过这个时长还没可用的连接则发生SQLException， 缺省:30秒
connectionTimeout = 30000
# 一个连接idle状态的最大时长（毫秒），超时则被释放（retired），缺省:10分钟
idleTimeout = 600000
# 一个连接的生命时长（毫秒），超时而且没被使用则被释放（retired），缺省:30分钟，建议设置比数据库超时时长少30秒，参考MySQL wait_timeout参数（show variables like '%timeout%';）
maxLifetime = 1800000
# 获取连接前的测试SQL
connectionTestQuery = SELECT 1
# 最小闲置连接数
minimumIdle = 10
# 连接池中允许的最大连接数。缺省值：10；推荐的公式：((core_count * 2) + effective_spindle_count)
maximumPoolSize = 10
# 连接只读数据库时配置为true， 保证安全
readOnly = false
</code></pre>
<h3>3. 获取数据源</h3> 
<pre><code>//获取默认数据源
DataSource ds = DSFactory.get()
</code></pre>
<p>是滴，就是这么简单，一个简单的方法，可以识别数据源并读取默认路径(<code>config/db.setting</code>)下信息从而获取数据源。</p>
<h3>4. 直接创建数据源</h3>
<p>当然你依旧可以按照连接池本身的方式获取数据源对象。我们以Druid为例：<br> </p>
<pre><code><br>//具体的配置参数请参阅Druid官方文档
DruidDataSource ds2 = new DruidDataSource();
ds2.setUrl("jdbc:mysql://localhost:3306/dbName");
ds2.setUsername("root");
ds2.setPassword("123456");
</code></pre>
<p></p>
<h3>5. 创建简单数据源</h3>
<p>有时候我们的操作非常简单，亦或者只是测试下远程数据库是否畅通，我们可以使用Hutool提供的<code>SimpleDataSource</code>:</p> 
<pre><code>DataSource ds = new SimpleDataSource("jdbc:mysql://localhost:3306/dbName", "root", "123456");
</code></pre>
<p>SimpleDataSource只是<code>DriverManager.getConnection</code>的简单包装，本身并不支持池化功能，此类特别适合少量数据库连接的操作。</p>
<p>同样的，SimpleDataSource也支持默认配置文件：<br> </p>
<pre><code><br>DataSource ds = new SimpleDataSource();
</code></pre>
<p></p>
<h2>高级实用</h2>
<h3>1. 自定义连接池</h3>
<p>有时候当项目引入多种数据源时，我们希望自定义需要的连接池，此时可以：<br> </p>
<pre><code><br>//自定义连接池实现为Tomcat-pool
DSFactory.setCurrentDSFactory(new TomcatDSFactory());
DataSource ds = DSFactory.get();
</code></pre>
<p></p>
<p>需要注意的是，<code>DSFactory.setCurrentDSFactory</code>是一个全局方法，必须在所有获取数据源的时机之前调用，调用一次即可（例如项目启动）。</p>
<h3>2. 自定义配置文件</h3>
<p>有时候由于项目规划的问题，我们希望自定义数据库配置Setting的位置，甚至是动态加载Setting对象，此时我们可以使用以下方法从其它的Setting对象中获取数据库连接信息：</p> 
<pre><code>//自定义数据库Setting，更多实用请参阅Hutool-Setting章节
Setting setting = new Setting("otherPath/other.setting");
//获取指定配置，第二个参数为分组，用于多数据源，无分组情况下传null
DataSource ds = DSFactory.get(setting, null);
</code></pre>
<h3>3. 多数据源</h3>
<p>有的时候我们需要操作不同的数据库，也有可能我们需要针对线上、开发和测试分别操作其数据库，无论哪种情况，Hutool都针对多数据源做了很棒的支持。</p>
<p>多数据源有两种方式可以实现：</p>
<h4>1. 多个配置文件分别获得数据源</h4>
<p>就是按照自定义配置文件的方式读取多个配置文件即可。</p>
<h4>2. 在同一配置文件中使用分组隔离不同的数据源配置：</h4> 
<pre><code>[group_db1]
url = jdbc:mysql://&lt;host&gt;:&lt;port&gt;/&lt;database_name&gt;
username = 用户名
password = 密码

[group_db2]
url = jdbc:mysql://&lt;host2&gt;:&lt;port&gt;/&lt;database_name&gt;
username = 用户名
password = 密码
</code></pre>
<p>我们按照上面的方式编写<code>db.setting</code>文件，然后：</p> 
<pre><code>DataSource ds1 = DSFactory.get("group_db1");
DataSource ds2 = DSFactory.get("group_db2");
</code></pre>
<p>这样我们就可以在一个配置文件中实现多数据源的配置。</p>
<h2>结语</h2>
<p>Hutool通过多种方式获取DataSource对象，获取后除了可以在Hutool自身应用外，还可以将此对象传入不同的框架以实现无缝结合。</p>
<p>Hutool对数据源的封装很好的诠释了以下几个原则：<br>1. 自动识别优于用户定义<br>2. 便捷性与灵活性并存<br>3. 适配与兼容</p>
			</div>

																						<h3 id="text_216013" class="sin_target">简单CRUD-SqlRunner</h3>
<div class="sin_text">
    					<h2>由来</h2>
<p>在最基本的数据库编码中，最常用的要数CRUD（既增查改删，也叫增删改查）。在Hutoo-db模块中，把这些逻辑全部封装于<code>SqlRunner</code>中。</p>
<h2>使用</h2>
<h3>1. 创建SqlRunner</h3>
<p>在上一章节中，我们详细的介绍了如何获取DataSource对象，而SqlRunner的构造就是依赖于DataSource:</p> 
<pre><code>Datasource ds = DSFactory.get();
SqlRunner runner = SqlRunner.create(ds);
</code></pre>
<p>非常简单的两句就可以构造SqlRunner对象，当然，Hutool还针对默认配置文件，进一步简化：</p> 
<pre><code>SqlRunner runner = SqlRunner.create();
</code></pre>
<p>这就是约定大于配置的优势：代码可以做到及其精简。</p>
<h3>2. 对数据库操作</h3>
<p>在<code>SqlRunner</code>中，提供了丰富的方法来操作数据库的增删改查。</p>
<p>需要说明的是，Hutool-db吸取了Jfinal的ActiveRecord思想，并从Python语言的Django框架中学习部分思想，因此SqlRunner中操作数据库传入的数据和条件都为<code>Entity</code>对象——一个继承于Map的动态K-V对象。这个对象中key为字段名，value为字段值。通过这种方式，我们读取和插入的数据可以不考虑类型（Hutool会自动识别并处理相应类型）。</p>
<p>具体操作如下：</p>
<h4>1. 插入数据</h4> 
<pre><code>//runner既SqlRunner对象
int count = runner.insert(Entity.create("user")
    .set("name", "unitTestUser")
    .set("age", 66));
</code></pre>
<p>以上我们构建了一个Entity对象，对应数据库中的<code>user</code>表，set入各字段值。</p>
<h4>2. 插入数据并返回主键</h4>
<p>很多时候我们的表主键为自增主键，每次插入希望返回主键，我们可以：<br> </p>
<pre><code><br>Long id = runner.insertForGeneratedKey(Entity.create("user")
    .set("name", "张三")
    .set("age", 66));
</code></pre>
<p></p>
<h4>3. 批量插入</h4> 
<pre><code>User user1 = new User();
user1.setName("张三");
user1.setAge(12);
user1.setBirthday("19900112");
user1.setGender(true);

User user2 = new User();
user2.setName("李四");
user2.setAge(12);
user2.setBirthday("19890512");
user2.setGender(false);

Entity data1 = Entity.parse(user1);
Entity data2 = Entity.parse(user2);

int[] result = runner.insert(CollectionUtil.newArrayList(data1, data2));
</code></pre>
<p>需要注意的是，批量插入每一条数据结构必须一致。批量插入数据时会获取第一条数据的字段结构，之后的数据会按照这个格式插入。也就是说假如第一条数据只有2个字段，后边数据多于这两个字段的部分将被抛弃。</p>
<h4>4. 修改数据</h4> 
<pre><code>int update = runner.update(
    Entity.create().set("age", 88), 
    Entity.create("user").set("name", "unitTestUser")
);
</code></pre>
<p>修改数据我们构建了两个Entity对象，第一个表示要修改的数据项和数据值，第二个表示where条件（要修改哪些数据）。表名可以在两个Entity对象中的任意一个中设置。最终生成的SQL：</p> 
<pre><code>UPDATE user SET age = ? WHERE name = ?
</code></pre>
<p>值通过<code>PreparedStatemnt</code>方式注入（最大限度的避免SQL注入）</p> 
<blockquote>
 <p>** 小提示 **<br>条件语句除了可以用<code>=</code>精确匹配外，也可以范围条件匹配，例如表示 <code>age &lt; 12</code> 可以这样构造Entity：<code>Entity.create("user").set("age", "&lt; 12")</code>，但是通过Entity方式传入条件暂时不支持同字段多条件的情况。</p> 
</blockquote>
<h4>5. 删除数据</h4> 
<pre><code>int delCount = runner.del(Entity.create("user").set("name", "unitTestUser"));
</code></pre>
<p>同样的，删除的条件也支持范围条件匹配。最终生成的SQL：</p> 
<pre><code>DELETE FROM user WHERE name = ?
</code></pre> 
<blockquote>
 <p>** 小提示 **<br>为了安全，Hutool并不支持删除操作传入空条件（既生成SQL后无WHERE条件），这样可以一定程度上避免传入null导致全表清空的事故。</p> 
</blockquote>
<h4>6. 数据查询</h4>
<p>数据查询主要针对条件查询、分页查询等功能的封装，Hutool查询支持以下几种数据库：</p> 
<ul> 
 <li>MySQL</li> 
 <li>Sqllite</li> 
 <li>Oracle</li> 
 <li>PostgreSQL</li> 
</ul>
<p>如果不能识别为以上几种数据库，则会按照ANSI标准生成SQL语句。</p>
<h5>(1). 条件查询</h5>
<p><code>find</code>方法可以按照指定条件查询数据库，方法定义如下：<br> </p>
<pre><code><br>T find(Collection&lt;String&gt; fields, Entity where, RsHandler&lt;T&gt; rsh)
</code></pre>
<br>第一个参数表示返回的字段列表，第二个参数为条件，第三个参数为结果集处理类（JDBC返回ResultSet，此对象定义了ResultSet转换为其它对象的逻辑）
<p></p>
<p>字段列表可以传入<code>null</code>表示查询所有字段（相当于select *），条件为Entity，与其它方法的条件方法一致。</p>
<p>结果集Hutool提供了几种默认的结果集：</p> 
<ul> 
 <li><code>EntityHandler</code> Entity对象处理器，只处理第一条数据。既当只需要一条数据时，可以使用此处理器，返回一个Entity对象。</li> 
 <li><code>EntityListHandler</code> 返回Entity列表，用于返回多条数据</li> 
 <li><code>EntitySetHandler</code> 返回Entity列表，用于返回多条不重复数据</li> 
 <li><code>NumberHandler</code> 处理为数字结果，当查询结果为单个数字时使用此处理器（例如select count(1)）</li> 
 <li><code>PageResultHandler</code> 分页结果集处理类 ，处理出的结果为PageResult（PageResult中有：页码、每页结果数、总页数、总数等信息）</li> 
</ul>
<p>栗子：<br> </p>
<pre><code><br>runner.find(
    CollectionUtil.newArrayList("name", "age"),
    Entity.create("user").set("name", "unitTestUser")
    new EntityListHandler()
);
</code></pre>
<p></p>
<h5>(2). 查询全部字段</h5> 
<pre><code>runner.find(
    Entity.create("user").set("name", "unitTestUser")
    new EntityListHandler()
);
</code></pre>
<p>对于EntityListHandler处理方式还有快捷方法：<br> </p>
<pre><code><br>runner.findAll(Entity.create("user").set("name", "unitTestUser"));
</code></pre>
<p></p>
<p>查询全部数据(数据量大慎用)：<br> </p>
<pre><code><br>runner.findAll("user");
</code></pre>
<p></p>
<h5>(3). 单条件查询</h5> 
<pre><code>runner.findBy("user", "name", "unitTestUser");
</code></pre>
<h5>(4). 模糊查询</h5> 
<pre><code>runner.findLike("user", "name", "Test", LikeType.Contains)
</code></pre>
<p>这句生成的SQL语句为：<br> </p>
<pre><code><br>SELECT * FROM user WHERE name like '%Test%'
</code></pre>
<p></p>
<h5>(5). 分页</h5>
<p>分页的使用方法类似于find方法，只是多了页码(page)和每页条目数(numPerPage)两个参数用于控制分页，Hutool的分页支持MySQL、Sqllite、Oracle等主流数据库。</p>
<p>栗子：<br> </p>
<pre><code><br>//Page对象通过传入页码和每页条目数达到分页目的
PageResult&lt;Entity&gt; result = runner.page(Entity.create("user").set("age", "&gt; 30"), new Page(10, 20));
</code></pre>
<p></p>
<p>其中PageResult继承自ArrayList，本身是个List可以按照列表操作，同时提供方法获取总页数、总数等信息方便前端生成分页列表。</p>
<h5>(6). SQL执行</h5>
<p>由于Hutool的封装操作功能有限，并不能满足复杂的SQL执行操作（例如多表联查、同字段多条件等），因此Hutool提供了一系列方法用于执行SQL语句：</p> 
<ul> 
 <li><code>query</code> 通过传入SQL、结果集处理器和SQL参数便捷的执行SQL查询</li> 
 <li><code>execute</code> 执行非查询SQL语句</li> 
 <li><code>executeForGeneratedKey</code> 执行非查询语句并返回主键</li> 
 <li><code>executeBatch</code> 执行批量插入，JDBC批量查询的浅封装，数据列表中的字段值列表格式必须一致</li> 
</ul>
			</div>

																						<h3 id="text_216014" class="sin_target">案例-导出Blob字段图像</h3>
<div class="sin_text">
    					<h2>需求：</h2>
<p>有一张单表存储着图片（图片使用Blob字段）以及图片的相关信息，需求是从数据库中将这些Blob字段内容保存为图片文件，文件名为图片的相关信息。</p>
<h2>环境</h2>
<p>数据库：Oracle<br>本地：Windows<br>工具：Hutool-db模块</p>
<h2>编码</h2>
<h3>数据库配置：<code>src/main/resources/config/db.setting</code></h3> 
<pre><code># -------------------------------------------------------------
# ----- Setting File with UTF8-----
# ----- 数据库配置文件 -----
# -------------------------------------------------------------

#JDBC url，必须
url = jdbc:oracle:thin:@192.168.1.1:1521/orcl
#用户名，必须
user = test
#密码，必须，如果密码为空，请填写 pass = 
pass = test
</code></pre>
<h3>代码：<code>PicTransfer.java</code></h3> 
<pre><code>import java.sql.Blob;
import java.sql.ResultSet;
import java.sql.SQLException;

import com.xiaoleilu.hutool.db.Entity;
import com.xiaoleilu.hutool.db.SqlRunner;
import com.xiaoleilu.hutool.db.ds.DSFactory;
import com.xiaoleilu.hutool.db.handler.RsHandler;
import com.xiaoleilu.hutool.io.FileUtil;
import com.xiaoleilu.hutool.util.CollectionUtil;
import com.xiaoleilu.hutool.util.StrUtil;

public class PicTransfer {
	private static SqlRunner runner = SqlRunner.create();
	private static String destDir = "f:/pic";
	
	public static void main(String[] args) throws SQLException {
		runner.find(
				CollectionUtil.newArrayList("NAME", "TYPE", "GROUP", "PIC"), 
				Entity.create("PIC_INFO").set("TYPE", 1),
				new RsHandler&lt;String&gt;(){
					@Override
					public String handle(ResultSet rs) throws SQLException {
						while(rs.next()){
							save(rs);
						}
						return null;
					}
				}
		);
	}
	
	private static void save(ResultSet rs) throws SQLException{
		String path = StrUtil.format("{}/{}-{}.jpg", destDir, rs.getString("NAME"), rs.getString("GROUP"));
		FileUtil.writeFromStream(rs.getBlob("PIC").getBinaryStream(), path);
	}
}
</code></pre>
			</div>

																														<h2 id="category_52951">http客户端(Hutool-http)</h2>
																								<h3 id="text_216015" class="sin_target">概述</h3>
<div class="sin_text">
    					<h2>由来</h2>
<p>在Java的世界中，Http客户端之前一直是Apache家的HttpClient占据主导，但是由于此包较为庞大，API又比较难用，因此并不使用很多场景。而新兴的OkHttp、Jodd-http固然好用，但是面对一些场景时，学习成本还是有一些的。很多时候，我们想追求轻量级的Http客户端，并且追求简单易用。而JDK自带的HttpUrlConnection可以满足大部分需求。Hutool针对此类做了一层封装，使Http请求变得无比简单。</p>
<h2>介绍</h2>
<p>Hutool-http针对JDK的HttpUrlConnection做一层封装，简化了HTTPS请求、文件上传、Cookie记忆等操作，使Http请求变得无比简单。</p>
<p>Hutool-http的核心集中在两个类：<br>- HttpRequest<br>- HttpResponse</p>
<p>同时针对大部分情境，封装了HttpUtil工具类。</p>
<h3>Hutool-http优点</h3> 
<ol> 
 <li>根据URL自动判断是请求HTTP还是HTTPS，不需要单独写多余的代码。</li> 
 <li>表单数据中有File对象时自动转为<code>multipart/form-data</code>表单，不必单做做操作。</li> 
 <li>默认情况下Cookie自动记录，比如可以实现模拟登录，即第一次访问登录URL后后续请求就是登录状态。</li> 
 <li>自动识别304跳转并二次请求</li> 
 <li>自动识别页面编码，即根据header信息或者页面中的相关标签信息自动识别编码，最大可能避免乱码。</li> 
 <li>自动识别并解压Gzip格式返回内容</li> 
</ol>
<h2>使用</h2>
<p>最简单的使用莫过于用HttpUtil工具类快速请求某个页面：</p> 
<pre><code>//GET请求
String content = HttpUtil.get(url);
</code></pre>
<p>一行代码即可搞定，当然Post请求也很简单：</p> 
<pre><code>//POST请求
HashMap&lt;String, Object&gt; paramMap = new HashMap&lt;&gt;();
paramMap.put("city", "北京");

String result1 = HttpUtil.post(url, paramMap);
</code></pre>
<p>Post请求只需使用Map预先制定form表单项即可。</p>
<h2>更多</h2>
<p>根据Hutool的“便捷性与灵活性并存”原则，HttpUtil的存在体现了便捷性，那HttpRequest对象的使用则体现了灵活性，使用此对象可以自定义更多的属性给请求，以适应Http请求中的不同场景（例如自定义header、自定义cookie、自定义代理等等）。相关类的使用请见下几个章节。</p>
			</div>

																						<h3 id="text_216016" class="sin_target">Http客户端工具类-HttpUtil</h3>
<div class="sin_text">
    					<h2>概述</h2>
<p>HttpUtil是应对简单场景下Http请求的工具类封装，这个工具类可以保证在一个方法之内完成Http请求</p>
<h2>使用</h2>
<h3>请求普通页面</h3>
<p>针对最为常用的GET和POST请求，HttpUtil封装了两个方法，</p> 
<ul> 
 <li><code>HttpUtil.get</code></li> 
 <li><code>HttpUtil.post</code></li> 
</ul>
<p>这两个方法用于请求普通页面，然后返回页面内容的字符串，同时提供一些重载方法用于指定请求参数（指定参数支持File对象，可实现文件上传，当然仅仅针对POST请求）。</p>
<p>GET请求栗子：</p> 
<pre><code>// 最简单的HTTP请求，可以自动通过header等信息判断编码，不区分HTTP和HTTPS
String result1= HttpUtil.get("https://www.baidu.com");

// 当无法识别页面编码的时候，可以自定义请求页面的编码
String result2= HttpUtil.get("https://www.baidu.com", "UTF-8");

//可以单独传入http参数，这样参数会自动做URL编码，拼接在URL中
HashMap&lt;String, Object&gt; paramMap = new HashMap&lt;&gt;();
paramMap.put("city", "北京");
String result3= HttpUtil.get("https://www.baidu.com", paramMap);
</code></pre>
<p>POST请求栗子：</p> 
<pre><code>HashMap&lt;String, Object&gt; paramMap = new HashMap&lt;&gt;();
paramMap.put("city", "北京");
String result= HttpUtil.post("https://www.baidu.com", paramMap);

//文件上传只需将参数中的键指定（默认file），值设为文件对象即可，对于使用者来说，文件上传与普通表单提交并无区别
paramMap.put("file", FileUtil.file("D:\\face.jpg"));
String result= HttpUtil.post("https://www.baidu.com", paramMap);
</code></pre>
<h3>下载文件</h3>
<p>因为Hutool-http机制问题，请求页面返回结果是一次性解析为byte[]的，如果请求URL返回结果太大（比如文件下载），那内存会爆掉，因此针对文件下载HttpUtil单独做了封装。文件下载在面对大文件时采用流的方式读写，内存中只是保留一定量的缓存，然后分块写入硬盘，因此大文件情况下不会对内存有压力。</p> 
<pre><code>String fileUrl = "http://mirrors.sohu.com/centos/7.3.1611/isos/x86_64/CentOS-7-x86_64-DVD-1611.iso";

//将文件下载后保存在E盘，返回结果为下载文件大小
long size = HttpUtil.downloadFile(fileUrl, FileUtil.file("e:/"));
System.out.println("Download size: " + size);
</code></pre>
<p>当然，如果我们想感知下载进度，还可以使用另一个重载方法回调感知下载进度：<br> </p>
<pre><code><br>//带进度显示的文件下载
HttpUtil.downloadFile(fileUrl, FileUtil.file("e:/"), new StreamProgress(){
	
	@Override
	public void start() {
		Console.log("开始下载。。。。");
	}
	
	@Override
	public void progress(long progressSize) {
		Console.log("已下载：{}", FileUtil.readableFileSize(progressSize));
	}
	
	@Override
	public void finish() {
		Console.log("下载完成！");
	}
});
</code></pre>
<p></p>
<p>StreamProgress接口实现后可以感知下载过程中的各个阶段。</p>
<p>当然，工具类提供了一个更加抽象的方法：<code>HttpUtil.download</code>，此方法会请求URL，将返回内容写入到指定的OutputStream中。使用这个方法，可以更加灵活的将HTTP内容转换写出，以适应更多场景。</p>
<h3>更多有用的工具方法</h3> 
<ul> 
 <li><code>HttpUtil.encode</code>和<code>HttpUtil.decode</code> 两个方法封装了JDK的<code>URLEncoder.encode</code>和<code>URLDecoder.decode</code>方法，可以方便的对URL参数进行URL编码和解码。</li> 
 <li><code>HttpUtil.toParams</code>和<code>HttpUtil.decodeParams</code> 两个方法是将Map参数转为URL参数字符串和将URL参数字符串转为Map对象</li> 
 <li><code>HttpUtil.urlWithForm</code>是将URL字符串和Map参数拼接为GET请求所用的完整字符串使用</li> 
 <li><code>HttpUtil.getMimeType</code> 根据文件扩展名快速获取其MimeType（参数也可以是完整文件路径）</li> 
 <li><code>HttpUtil.getClientIP</code> 根据指定Http头信息获取客户端IP地址，此方法适用于在Nginx转发时获取真实客户端地址的快捷方法（此方法依赖于Servlet-api）</li> 
</ul>
			</div>

																						<h3 id="text_216017" class="sin_target">Http客户端-HttpRequest</h3>
<div class="sin_text">
    					<h2>介绍</h2>
<p>本质山，HttpUtil中的get和post工具方法都是HttpRequest对象的封装，因此如果想更加灵活操作Http请求，可以使用HttpRequest。</p>
<h2>使用</h2>
<h3>普通表单</h3>
<p>我们以POST请求为例：</p> 
<pre><code>//链式构建请求
String result2 = HttpRequest.post(url)
	.header(Header.USER_AGENT, "Hutool http")
	.form(paramMap)
	.execute().body();
Console.log(result2);
</code></pre>
<p>通过链式构建请求，我们可以很方便的指定Http头信息和表单信息，最后调用execute方法即可执行请求，返回HttpResponse对象。HttpResponse包含了服务器响应的一些信息，包括响应的内容和响应的头信息。通过调用body方法即可获取响应内容。</p>
<h3>Restful请求</h3> 
<pre><code>String json = ...;
String result2 = HttpRequest.post(url)
	.body(json)
	.execute().body();
</code></pre>
<h2>其它自定义项</h2>
<p>同样，我们通过HttpResponse可以很方便的做以下操作：</p> 
<ul> 
 <li>指定请求头</li> 
 <li>自定义Cookie（cookie方法）</li> 
 <li>指定是否keepAlive（keepAlive方法）</li> 
 <li>指定表单内容（form方法）</li> 
 <li>指定请求内容，比如rest请求指定JSON请求体（body方法）</li> 
 <li>超时设置（timeout方法）</li> 
 <li>指定代理（setProxy方法）</li> 
 <li>指定SSL协议（setSSLProtocol）</li> 
 <li>简单验证（basicAuth方法）</li> 
</ul>
			</div>

																						<h3 id="text_216018" class="sin_target">Http响应封装-HttpResponse</h3>
<div class="sin_text">
    					<h2>介绍</h2>
<p>HttpResponse是HttpRequest执行execute()方法后返回的一个对象，我们可以通过此对象获取服务端返回的：</p> 
<ul> 
 <li>Http状态码（getStatus方法）</li> 
 <li>返回内容编码（contentEncoding方法）</li> 
 <li>是否Gzip内容（isGzip方法）</li> 
 <li>返回内容（body、bodyBytes、bodyStream方法）</li> 
 <li>响应头信息（header方法）</li> 
</ul>
<h2>使用</h2>
<p>此对象的使用非常简单，最常用的便是body方法，会返回字符串Http响应内容。如果想获取byte[]则调用bodyBytes即可。</p>
			</div>

																						<h3 id="text_216019" class="sin_target">常用Http状态码-HttpStatus</h3>
<div class="sin_text">
    					<h2>介绍</h2>
<p>针对Http响应，Hutool封装了一个类用于保存Http状态码</p>
<p>此类用于保存一些状态码的别名，例如：</p> 
<pre><code>/**
* HTTP Status-Code 200: OK.
*/
public static final int HTTP_OK = 200;
</code></pre>
			</div>

																						<h3 id="text_216020" class="sin_target">HTML工具类-HtmlUtil</h3>
<div class="sin_text">
    					<h2>由来</h2>
<p>针对Http请求中返回的Http内容，Hutool使用此工具类来处理一些HTML页面相关的事情。</p>
<h2>方法</h2> 
<ul> 
 <li><code>HtmlUtil.restoreEscaped</code> 还原被转义的HTML特殊字符</li> 
 <li><code>HtmlUtil.encode</code> 转义文本中的HTML字符为安全的字符</li> 
 <li><code>HtmlUtil.cleanHtmlTag</code> 清除所有HTML标签</li> 
 <li><code>HtmlUtil.removeHtmlTag</code> 清除指定HTML标签和被标签包围的内容</li> 
 <li><code>HtmlUtil.unwrapHtmlTag</code> 清除指定HTML标签，不包括内容</li> 
 <li><code>HtmlUtil.removeHtmlAttr</code> 去除HTML标签中的属性</li> 
 <li><code>HtmlUtil.removeAllHtmlAttr</code> 去除指定标签的所有属性</li> 
 <li><code>HtmlUtil.filter</code> 过滤HTML文本，防止XSS攻击</li> 
</ul>
			</div>

																														<h2 id="category_52952">定时任务(Hutool-cron)</h2>
																								<h3 id="text_216021" class="sin_target">定时任务模块概述</h3>
<div class="sin_text">
    					<h3>牢骚</h3>
<p>Java中定时任务使用的最多的我想就是<a href="http://www.quartz-scheduler.org/" rel="nofollow">quartz</a>了，但是这个框架太过庞大，而且我也不需要用到这么多东西，使用方法也是比较复杂（官方Demo我实在是无语……）。用过Linux的crontab的人都知道，使用其定时的表达式可以非常灵活的定义定时任务的时间以及频率（Linux的crontab精确到分，而quaeta的精确到秒，不过对我来说精确到分已经够用了，精确到秒的可以使用Timer可以搞定），然后就是crontab的那个迷人的配置文件，可以把定时任务很清晰的罗列出来，这个我也是比较喜欢的。（记得当时Spring整合quartz的时候那XML看的我眼都花了……）。于是Hutool-cron诞生。</p>
<h3>封装</h3>
<p>为了隐藏这个框架里面的东西，我对其做了封装，所谓封装，就是把任务调度放在一个配置文件里，然后启动即可（与Linux的crontab非常像）。</p>
<h4>配置文件</h4>
<p>对于Maven项目，首先在<code>src/main/resources/config</code>下放入cron.setting文件（默认是这个路径的这个文件），然后在文件中放入定时规则，规则如下：</p> 
<pre><code>#我是注释
[com.company.aaa.job]
TestJob.run = */10 * * * *
TestJob2.run = */10 * * * *
</code></pre>
<p>中括号里面表示包名，第二行等号前面是要执行的定时任务类名，等号后面是定时表达式。</p>
<p>关于定时任务表达式，它与Linux的crontab表达式一模一样，具体请看这里：<br><a href="http://www.cnblogs.com/peida/archive/2013/01/08/2850483.html" rel="nofollow">http://www.cnblogs.com/peida/archive/2013/01/08/2850483.html</a></p>
<p>中括号以下都是使用这个包的，直到遇到下一个包名，以上配置等同于：</p> 
<pre><code>com.company.aaa.job.TestJob.run = */10 * * * *
com.company.aaa.job.TestJob2.run = */10 * * * *
</code></pre>
<p>TestJob是一个实现了Runnable接口的类，在run()方法里就可以加逻辑代码了。</p> 
<blockquote>
 <p>3.x之后的cron模块是我自己实现的，job不再需要实现任何接口，也不需要继承任何类，即可在配置文件中通过配置**类名.方法名**执行。</p> 
</blockquote>
<h4>启动</h4>
<p>调用CronUtil.start()既可启动定时任务服务，CrontabUtil.stop()关闭服务。</p>
<h3>其他方法</h3>
<p>当然，如果你想动态的添加定时任务，使用CronUtil.schedule(String schedulingPattern, Runnable task)方法即可（使用此方法加入的定时任务不会被写入到配置文件）。</p>
			</div>

																														<h2 id="category_52953">扩展(Hutool-extra)</h2>
																								<h3 id="text_216022" class="sin_target">概述</h3>
<div class="sin_text">
    					<h2>由来</h2>
<p>由于Hutool的原则是不依赖于其它配置文件，但是很多时候我们需要针对第三方非常棒的库做一些工具类化的支持，因此Hutoo-extra包主要用于支持第三方库的工具类支持。</p>
			</div>

																														<h2 id="category_52954">布隆过滤(Hutool-bloomFilter)</h2>
																							<h3 id="text_216023" class="sin_target">概述</h3>
<div class="sin_text">
    </div>

																														<h2 id="category_52955">切面(Hutool-aop)</h2>
																								<h3 id="text_216024" class="sin_target">概述</h3>
<div class="sin_text">
    					<h2>Hutool-aop概述</h2>
<p>AOP模块主要针对JDK中动态代理进行封装，抽象动态代理为切面类<code>Aspect</code>，通过<code>ProxyUtil</code>代理工具类将切面对象与被代理对象融合，产生一个代理对象，从而可以针对每个方法执行前后做通用的功能。</p>
<p>在aop模块中，默认实现可以下两个切面对象：<br>1. SimpleAspect 简单切面对象，不做任何操作，继承此对象可重写需要的方法即可，不必实现所有方法<br>2. TimeIntervalAspect 执行时间切面对象，用于简单计算方法执行时间，然后通过日志打印方法执行时间</p> 
<blockquote>
 <p>由于AOP模块封装JDK的代理，故被代理对象必须实现接口。</p> 
</blockquote>
<h2>使用</h2> 
<ol> 
 <li>我们定义一个接接口：</li> 
</ol> 
<pre><code>public interface Animal{
	void eat();
}
</code></pre> 
<ol> 
 <li>定义一个实现类：</li> 
</ol> 
<pre><code>public class Cat implements Animal{

	@Override
	public void eat() {
		Console.log("猫吃鱼");
	}
	
}
</code></pre> 
<ol> 
 <li>我们使用<code>TimeIntervalAspect</code>这个切面代理上述对象，来统计猫吃鱼的执行时间：</li> 
</ol> 
<pre><code>Animal cat = ProxyUtil.proxy(new Cat(), TimeIntervalAspect.class);
cat.eat();
</code></pre>
<p>执行结果为：<br> </p>
<pre><code><br>猫吃鱼
Method [com.xiaoleilu.hutool.aop.test.AopTest$Cat.eat] execute spend [54]ms
</code></pre>
<p></p>
<p>这样我们就是实现了切面代理。</p> 
<blockquote>
 <p>在调用proxy方法后，IDE自动补全返回对象为Cat，因为JDK机制的原因，我们的返回值必须是被代理类实现的接口，因此需要手动将返回值改为**Animal**，否则会报类型转换失败。</p> 
</blockquote>
<h2>其它方法</h2>
<p>ProxyUtil中还提供了一些便捷的Proxy方法封装，例如newProxyInstance封装了Proxy.newProxyInstance方法，提供泛型返回值，并提供更多参数类型支持。</p>
<h2>原理</h2>
<p>动态代理对象的创建原理是：<br>假设创建的代理对象名为 $Proxy0<br>1. 根据传入的interfaces动态生成一个类，实现interfaces中的接口<br>2. 通过传入的classloder将刚生成的类加载到jvm中。即将$Proxy0类load<br>3. 调用$Proxy0的$Proxy0(InvocationHandler)构造函数 创建$Proxy0的对象，并且用interfaces参数遍历其所有接口的方法，并生成实现方法，这些实现方法的实现本质上是通过反射调用被代理对象的方法。<br>4. 将$Proxy0的实例返回给客户端。<br>5. 当调用代理类的相应方法时，相当于调用 InvocationHandler.invoke(Object, Method, Object []) 方法。</p>
			</div>

																														<h2 id="category_52956">脚本(Hutool-script)</h2>
																							<h3 id="text_216025" class="sin_target">概述</h3>
<div class="sin_text">
    </div>

																					<h3 id="text_216026" class="sin_target">JavaScript引擎</h3>
<div class="sin_text">
    </div>

																														<h2 id="category_52957">系统调用(Hutool-system)</h2>
																											<h2 id="category_52958">地理位置(Hutool-geo)</h2>
																											<h2 id="category_52960">Office文档操作（Hutool-poi）</h2>
																								<h3 id="text_216033" class="sin_target">概述</h3>
<div class="sin_text">
    					<h2>由来</h2>
<p>Java针对MS Office的操作的库屈指可数，比较有名的就是Apache的POI库。这个库异常强大，但是使用起来也并不容易。Hutool针对POI封装一些常用工具，使Java操作Excel等文件变得异常简单。</p>
<h2>介绍</h2>
<p>Hutool-poi是针对Apache POI的封装，因此需要用户自行引入POI库,Hutool默认不引入。到目前为止，Hutool-poi支持：</p> 
<ul> 
 <li>Excel文件（xls, xlsx）的读取</li> 
</ul>
<h2>使用</h2>
<h3>引入POI依赖</h3>
<p>推荐引入poi-ooxml，这个包会自动关联引入poi包，且可以很好的支持Office2007+的文档格式</p> 
<pre><code>&lt;dependency&gt;
    &lt;groupId&gt;org.apache.poi&lt;/groupId&gt;
    &lt;artifactId&gt;poi-ooxml&lt;/artifactId&gt;
    &lt;version&gt;${poi.version}&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>引入后即可使用Hutool的方法操作Office文件了，Hutool提供的类有：</p> 
<ul> 
 <li>ExcelUtil Excel工具类，读取的快捷方法都被封装于此</li> 
 <li>ExcelReader Excel读取器，Excel读取的封装，可以直接构造后使用。</li> 
</ul>
			</div>

																						<h3 id="text_216035" class="sin_target">ExcelUtil</h3>
<div class="sin_text">
    					<h2>介绍</h2>
<p>Excel操作工具封装</p>
<h2>使用</h2> 
<ol> 
 <li><p>从文件中读取Excel为ExcelReader<br> </p><pre><code><br>ExcelReader reader = ExcelUtil.getReader(FileUtil.file("test.xlsx"));
</code></pre><p></p></li> 
 <li><p>从流中读取Excel为ExcelReader（比如从ClassPath中读取Excel文件）<br> </p><pre><code><br>ExcelReader reader = ExcelUtil.getReader(new ClassPathResource("aaa.xlsx").getStream());
</code></pre><p></p></li> 
 <li><p>读取指定的sheet<br>```java<br>ExcelReader reader;</p></li> 
</ol>
<p>//通过sheet编号获取<br>reader = ExcelUtil.getReader(FileUtil.file(“test.xlsx”), 0);<br>//通过sheet名获取<br>reader = ExcelUtil.getReader(FileUtil.file(“test.xlsx”), “sheet1”);<br>```</p>
<h2>后续</h2>
<p><code>ExcelUtil.getReader</code>方法只是将实体Excel文件转换为ExcelReader对象进行操作。接下来请参阅章节ExcelReader对Excel工作簿进行具体操作。</p>
			</div>

																						<h3 id="text_216037" class="sin_target">ExcelReader</h3>
<div class="sin_text">
    					<h2>介绍</h2>
<p>读取Excel内容的封装，通过构造ExcelReader对象，指定被读取的Excel文件、流或工作簿，然后调用readXXX方法读取内容为指定格式。</p>
<h2>使用</h2> 
<ol> 
 <li><p>读取Excel中所有行和列，都用列表表示<br> </p><pre><code><br>ExcelReader reader = ExcelUtil.getReader("d:/aaa.xlsx");
List&lt;List&lt;Object&gt;&gt; readAll = reader.read();
</code></pre><p></p></li> 
 <li><p>读取为Map列表，默认第一行为标题行，Map中的key为标题，value为标题对应的单元格值。<br> </p><pre><code><br>ExcelReader reader = ExcelUtil.getReader("d:/aaa.xlsx");
List&lt;Map&lt;String,Object&gt;&gt; readAll = reader.readAll();
</code></pre><p></p></li> 
 <li><p>读取为Bean列表，Bean中的字段名为标题，字段值为标题对应的单元格值。<br> </p><pre><code><br>ExcelReader reader = ExcelUtil.getReader("d:/aaa.xlsx");
List&lt;Person&gt; all = reader.readAll(Person.class);
</code></pre><p></p></li> 
</ol>
			</div>

																									
				  		<div class="sin_info" style="margin-top: 10px;">
	  			版权所有 @路小磊 の Hutool project hutool.cn
	  		</div>
	  				</div>
		<div class="clear"></div>
	</div>
	<div id="bottom_begin"></div>
	<div class="sin_footer">
		<div>
			Powered by <a href="http://www.oschina.net" target="_blank">开源中国</a>
		</div>
	</div>
	<script>
		//计算图片的真实大小，如果超过编辑区域，则进行限制
		var resizePicWidth = function(pic_width){
			var imgs = document.getElementsByTagName("img");
            var j=0;
			for(var i=0;i<imgs.length;i++){
                var realWidth;	//真实的宽度
                var realHeight;		//真实的高度
                //这里做下说明，$("<img/>")这里是创建一个临时的img标签，类似js创建一个new Image()对象！
				var newImg = new Image();
				newImg.onload=function() {
                    realWidth = imgs[j].width;
                    realHeight = imgs[j].height;
                    //如果真实的宽度大于规定的宽度就按照100%显示
                    if(realWidth>=pic_width){
						imgs[j].style.width=(pic_width) + "px";
                    } else{//如果小于浏览器的宽度按照原尺寸显示
						imgs[j].style.width=realWidth+'px';
                    }
                    j++;
                }
				newImg.src=imgs[j].src;
			}

		}
		var currentNode = null;
		window.onscroll=function(){
            var h = document.getElementById("top_end").getBoundingClientRect().top;
            if(h<=0){
				document.getElementById("sin_navi_id").className='sin_navi sin_navi_nomove';
				document.getElementById("sin_content_id").className='sin_content sin_content_mar';
            }else{
                document.getElementById("sin_navi_id").className='sin_navi';
                document.getElementById("sin_content_id").className='sin_content';
            }
            comLayout();
            var arr= new Array();
            var index = 0;
			var sinTargets = getClass("h2","sin_target");
			for(var i=0;i<sinTargets.length;i++){
                var th = sinTargets[i].getBoundingClientRect().top - 80;	                if(th<=0){
                    arr[index] = new Array();
                    arr[index][0] = sinTargets[i].getAttribute("id");
                    arr[index][1] = th;
                    index++;
                }
			}

            var curr = bubbleSort(arr);
            if(curr!=null && curr!=currentNode){
				var oldCurrIds = getClass("li","sin_navi_current");
                if(oldCurrIds && oldCurrIds[0]){
                    var oid=oldCurrIds[0].getAttribute("id");
                    document.getElementById(oid).className=" ";
                }
				document.getElementById("navi_"+curr).className="sin_navi_current";
                currentNode = curr;
            }
        }
		
		
	    window.onresize = function(){
            comLayout();
		}

        var ch = document.getElementById("sin_navi_id").offsetHeight;

	    function comLayout() {
	        var h = document.documentElement.clientHeight;
	    	if(ch<h){
	    		return;
	    	}
		    var i = document.getElementById("bottom_begin").getBoundingClientRect().top;
	        if(i<h){
                document.getElementById("sin_navi_id").style.height=(i+"px");
		   	}else{
                document.getElementById("sin_navi_id").style.height=(h+"px");
		   	}
	    }
	    
	    function bubbleSort(arr){
	    	var i= arr.length;
	    	if(i<=0){
	    		return null;
	    	}
	    	var j;
			var tempExchangVal;
			while(i>0){
				for(j=0;j<i-1;j++){
					if(arr[j][1] < arr[j+1][1]){
						tempExchangVal=arr[j];
						arr[j]=arr[j+1];
						arr[j+1]=tempExchangVal;
					}
				}
				i--;
			}
			return arr[0][0];
		}
	    comLayout();
	    resizePicWidth(680);

        function getClass(tagname, className) { //tagname指元素，className指class的值
            //判断浏览器是否支持getElementsByClassName，如果支持就直接的用
            if (document.getElementsByClassName) {
                return document.getElementsByClassName(className);
            }
            else {    //当浏览器不支持getElementsByClassName的时候用下面的方法
                var tagname = document.getElementsByTagName(tagname);  //获取指定元素
                var tagnameAll = [];     //这个数组用于存储所有符合条件的元素
                for (var i = 0; i < tagname.length; i++) {     //遍历获得的元素
                    if (tagname[i].className == className) {     //如果获得的元素中的class的值等于指定的类名，就赋值给tagnameAll
                        tagnameAll[tagnameAll.length] = tagname[i];
                    }
                }
                return tagnameAll;
            }
        }
	</script>
</body>
</html>	
<!-- Generated by OsChina.NET (init:0[ms],page:548[ms],ip:119.123.179.1) -->